This R script is used to validate the points in the ReSurvey database using RS indicators (NDVI, NDMI, canopy height).

Load libraries

library(tidyverse)
library(here)
library(gridExtra)
G3;
Adjuntando el paquete: ‘gridExtra’

gG3;The following object is masked from ‘package:dplyr’:

    combine

g
library(readxl)
library(scales)
G3;
Adjuntando el paquete: ‘scales’

gG3;The following object is masked from ‘package:purrr’:

    discard

gG3;The following object is masked from ‘package:readr’:

    col_factor

g
library(sf)
G3;Linking to GEOS 3.13.1, GDAL 3.10.2, PROJ 9.5.1; sf_use_s2() is TRUE
g
library(rnaturalearth)
library(dtplyr)
G3;Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
g
library(lme4)
G3;Cargando paquete requerido: Matrix
gG3;
Adjuntando el paquete: ‘Matrix’

gG3;The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack

g
library(lmerTest)
G3;
Adjuntando el paquete: ‘lmerTest’

gG3;The following object is masked from ‘package:lme4’:

    lmer

gG3;The following object is masked from ‘package:stats’:

    step

g
library(car)
G3;Cargando paquete requerido: carData
gG3;
Adjuntando el paquete: ‘car’

gG3;The following object is masked from ‘package:dplyr’:

    recode

gG3;The following object is masked from ‘package:purrr’:

    some

g
library(ggeffects)
library(party)
G3;Cargando paquete requerido: grid
gG3;Cargando paquete requerido: mvtnorm
gG3;Cargando paquete requerido: modeltools
gG3;Cargando paquete requerido: stats4
gG3;
Adjuntando el paquete: ‘modeltools’

gG3;The following object is masked from ‘package:car’:

    Predict

gG3;The following object is masked from ‘package:lme4’:

    refit

gG3;Cargando paquete requerido: strucchange
gG3;Cargando paquete requerido: zoo
gG3;
Adjuntando el paquete: ‘zoo’

gG3;The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric

gG3;Cargando paquete requerido: sandwich
gG3;
Adjuntando el paquete: ‘strucchange’

gG3;The following object is masked from ‘package:stringr’:

    boundary

gG3;
Adjuntando el paquete: ‘party’

gG3;The following object is masked from ‘package:dplyr’:

    where

g
library(partykit)
G3;Cargando paquete requerido: libcoin
gG3;
Adjuntando el paquete: ‘partykit’

gG3;The following objects are masked from ‘package:party’:

    cforest, ctree, ctree_control, edge_simple, mob, mob_control, node_barplot, node_bivplot, node_boxplot,
    node_inner, node_surv, node_terminal, varimp

g
library(strucchange)
library(ggparty)
G3;
Adjuntando el paquete: ‘ggparty’

gG3;The following object is masked from ‘package:ggeffects’:

    get_predictions

g
library(caret)
G3;Cargando paquete requerido: lattice
gG3;
Adjuntando el paquete: ‘caret’

gG3;The following object is masked from ‘package:purrr’:

    lift

g
library(moreparty)
G3;Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
g
library(randomForest)
G3;randomForest 4.7-1.2
gG3;Type rfNews() to see new features/changes/bug fixes.
gG3;
Adjuntando el paquete: ‘randomForest’

gG3;The following object is masked from ‘package:gridExtra’:

    combine

gG3;The following object is masked from ‘package:dplyr’:

    combine

gG3;The following object is masked from ‘package:ggplot2’:

    margin

g
library(pROC)
G3;Type 'citation("pROC")' for a citation.
gG3;
Adjuntando el paquete: ‘pROC’

gG3;The following objects are masked from ‘package:stats’:

    cov, smooth, var

g

Define printall function

printall <- function(tibble) {
  print(tibble, width = Inf)
  }

Load geom_flat_violin plot

source("https://gist.githubusercontent.com/benmarwick/2a1bb0133ff568cbe28d/raw/fb53bd97121f7f9ce947837ef1a4c65a73bffb3f/geom_flat_violin.R")

Read ReSurvey data with RS indicators

db_resurv_RS<-read_tsv(
  here("data", "clean", "db_resurv_RS_20250523.csv"),
  col_types = cols(
    # Dynamically specify EUNIS columns as character
    .default = col_guess(),  # Default guessing for other columns
    EUNISa = col_character(),
    EUNISb = col_character(),
    EUNISc = col_character(),
    EUNISd = col_character(),
    EUNISa_1 = col_character(),
    EUNISa_2 = col_character(),
    EUNISa_3 = col_character(),
    EUNISa_4 = col_character(),
    EUNISb_1 = col_character(),
    EUNISb_2 = col_character(),
    EUNISb_3 = col_character(),
    EUNISb_4 = col_character(),
    EUNISc_1 = col_character(),
    EUNISc_2 = col_character(),
    EUNISc_3 = col_character(),
    EUNISc_4 = col_character(),
    EUNISd_1 = col_character(),
    EUNISd_2 = col_character(),
    EUNISd_3 = col_character(),
    EUNISd_4 = col_character(),
    EUNISa_1_descr = col_character(),
    EUNISb_1_descr = col_character(),
    EUNISc_1_descr = col_character(),
    EUNISd_1_descr = col_character(),
    EUNIS_assignation = col_character(),
    EUNISa_2_descr = col_character(),
    EUNISa_3_descr = col_character(),
    EUNISa_4_descr = col_character(),
    EUNISb_2_descr = col_character(),
    EUNISb_3_descr = col_character(),
    EUNISb_4_descr = col_character(),
    EUNISc_2_descr = col_character(),
    EUNISc_3_descr = col_character(),
    EUNISc_4_descr = col_character(),
    EUNISd_2_descr = col_character(),
    EUNISd_3_descr = col_character(),
    EUNISd_4_descr = col_character()
    )
  )

No parsing issues!

Some data managenemt

Several EUNIS level 1 assigned

Number of rows where there is more than one EUNIS 1 assigned, and they are different among them. See what to do with these later! So far I take EUNISa_1.

nrow(db_resurv_RS %>% 
       # Rows with more than one EUNIS 1 assigned
       filter(!is.na(EUNISb_1)) %>% 
       filter(EUNISa_1!=EUNISb_1 | EUNISb_1 != EUNISc_1 | EUNISa_1 != EUNISc_1))
[1] 102

See “confusions”:

db_resurv_RS %>% 
  # Rows with more than one EUNIS 1 assigned
  filter(!is.na(EUNISb_1)) %>% 
  filter(EUNISa_1!=EUNISb_1 | EUNISb_1 != EUNISc_1 | EUNISa_1 != EUNISc_1) %>%
  distinct(EUNISa_1, EUNISb_1, EUNISc_1, EUNISd_1)

Define “confusion” columns:

db_resurv_RS <- db_resurv_RS %>%
  mutate(EUNIS1_conf_type = case_when(
    EUNISa_1 == "R" & EUNISb_1 == "S" ~ "R/S",
    EUNISa_1 == "S" & EUNISb_1 == "T" ~ "S/T",
    EUNISa_1 == "R" & EUNISb_1 == "R" & EUNISc_1 == "S" ~ "R/S",
    EUNISa_1 == "R" & EUNISb_1 == "R" & EUNISc_1 == "S" & EUNISd_1 == "S" ~ "R/S",
    EUNISa_1 == "P" & EUNISb_1 == "Q" ~ "P/Q",
    TRUE ~ NA_character_),
    EUNIS1_conf = !is.na(EUNIS1_conf_type))

Tibble with selected columns

db_resurv_RS_short <- db_resurv_RS %>%
  select(PlotObservationID, Country, RS_CODE, `ReSurvey site`, `ReSurvey plot`,
         `Manipulate (y/n)`, `Type of manipulation`, Lon_updated, Lat_updated,
         `Location method`, `Location uncertainty (m)`, EUNISa_1,
         EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, EUNISa_3, EUNISa_3_descr,
         EUNISa_4, EUNISa_4_descr, EUNIS1_conf, EUNIS1_conf_type,
         date, year, biogeo, unit, year_RS, Lon_RS, Lat_RS, 
         starts_with("NDVI"), starts_with("NDMI"), starts_with("NDWI"),
         starts_with("EVI"), starts_with("SAVI"), canopy_height,
         SOS_DOY, SOS_date, NDVI_at_SOS, Peak_DOY, Peak_date, NDVI_at_Peak,
         EOS_DOY, EOS_date, NDVI_at_EOS, Season_Length,
         S2_data, Landsat_data, CH_data, S2_phen_data)

TO-DO: Missing data checks

Do when all RS data is ready!

Flag when year is different between RS data and ReSurvey db

db_resurv_RS_short <- db_resurv_RS_short %>%
  mutate(year_diff = year != year_RS)
db_resurv_RS_short %>% count(year_diff)

None with different year so far.

Flag when coordinates are different between RS data and ReSurvey db

db_resurv_RS_short <- db_resurv_RS_short %>%
  mutate(Lon_diff = case_when(Lon_updated == Lon_RS ~ "NO",
                              # Sometimes they are only slighly different
                              abs(Lon_updated - Lon_RS) < 0.01 ~ "SMALL",
                              is.na(Lon_updated) | is.na(Lon_RS) ~ NA,
                              TRUE ~ "LARGE"),
         Lat_diff = case_when(Lat_updated == Lat_RS ~ "NO",
                              # Sometimes they are only slighly different
                              abs(Lat_updated - Lat_RS) < 0.01 ~ "SMALL",
                              is.na(Lat_updated) | is.na(Lat_RS) ~ NA,
                              TRUE ~ "LARGE"))
db_resurv_RS_short %>% count(Lon_diff)
db_resurv_RS_short %>% count(Lat_diff)

Very few with large differences (3 for longitude, 5 for latitude). RS indices would need to be calculated again for those.

If year_diff is TRUE or Lon_diff is LARGE or Lat_diff is LARGE –> RS indices need to be recalculated. So far, remove those rows from db_resurv_RS_short.

db_resurv_RS_short <- db_resurv_RS_short %>%
  filter(year_diff == FALSE | is.na(year_diff)) %>%
  filter(Lon_diff == "NO" |Lon_diff == "SMALL" | is.na(Lon_diff)) %>%
  filter(Lat_diff == "NO" |Lat_diff == "SMALL" | is.na(Lat_diff))

TO-DO: Recalculate RS indices for those above

Handle plots that have more than one obs per year

Add column PLOT to data to identify unique plots:

db_resurv_RS_short_PLOT <- db_resurv_RS_short %>%
  # Original names give problems, create new vars
  mutate(RS_site = `ReSurvey site`, RS_plot = `ReSurvey plot`) %>%
  # Convert to data.table for faster processing
  lazy_dt() %>%
  # Group by the 3 vars that uniquely identify each pl ot
  group_by(RS_CODE, RS_site, RS_plot) %>%
  # Create a new variable PLOT for each group
  mutate(PLOT = .GRP) %>%
  # Convert back to tibble
  as_tibble() %>%
  # Remove unneeded vars
  select(-RS_site, -RS_plot)

There should be only one observation of each plot per year.

Plots where there is at least a year with more than one observation, and where those observations have a different EUNIS assigned:

plots_to_remove <- db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  summarize(EUNISa_1_n = n_distinct(EUNISa_1, na.rm = TRUE)) %>%
  ungroup() %>% 
  filter(EUNISa_1_n > 1) %>%
  distinct(PLOT)
`summarise()` has grouped output by 'PLOT'. You can override using the `.groups` argument.

Remove plots_to_remove from the database:

db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
  anti_join(plots_to_remove, by = "PLOT")

Plots and years where there is more than one observation:

plots_to_merge <- db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  # Plots that have more than one observation per year
  filter(n() > 1) %>%
  ungroup() %>%
  distinct(PLOT)

Summarize plots_to_merge:

plots_to_merge_summ <- db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  # Plots that have more than one observation per year
  filter(n() > 1) %>%
  mutate(obs_num = row_number()) %>%
  pivot_wider(
    names_from = obs_num,
    values_from = c(date, PlotObservationID),
    names_prefix = "obs_"
  ) %>%
  arrange(PLOT) %>%
  summarize(
    across(c(Country, RS_CODE, `ReSurvey site`, `ReSurvey plot`,
             `Manipulate (y/n)`, `Type of manipulation`, Lon_updated,
             Lat_updated, `Location method`, `Location uncertainty (m)`,
             EUNISa_1, EUNISa_1_descr, EUNISa_2, EUNISa_2_descr, EUNISa_3,
             EUNISa_3_descr, EUNISa_4, EUNISa_4_descr, EUNIS1_conf,
             EUNIS1_conf_type, biogeo, unit, year_RS, Lon_RS, Lat_RS, NDVI_max,
             NDVI_median, NDVI_min, NDVI_mode, NDVI_p10, NDVI_p90, NDMI_max,
             NDMI_median, NDMI_min, NDMI_mode, NDMI_p10, NDMI_p90, NDWI_max,
             NDWI_median, NDWI_min, NDWI_mode, NDWI_p10, NDWI_p90, EVI_max,
             EVI_median, EVI_min, EVI_mode, EVI_p10, EVI_p90, SAVI_max,
             SAVI_median, SAVI_min, SAVI_mode, SAVI_p10, SAVI_p90,
             canopy_height, SOS_DOY, SOS_date, Peak_DOY, Peak_date, EOS_DOY,
             EOS_date, S2_data, Landsat_data, CH_data, S2_phen_data, year_diff,
             Lon_diff, Lat_diff), first),
    across(starts_with("date_obs_"), min),
    across(starts_with("PlotObservationID_obs_"), min)
    ) %>%
  ungroup()
`summarise()` has grouped output by 'PLOT'. You can override using the `.groups` argument.

Remove plots_to_merge from the database:

db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
  anti_join(plots_to_merge, by = "PLOT")

And add plots_to_merge_summ, where each plot and year only has one row:

db_resurv_RS_short_PLOT <- bind_rows(db_resurv_RS_short_PLOT,
                                     plots_to_merge_summ)

Check that there is only one row per plot and per year:

db_resurv_RS_short_PLOT %>%
  group_by(PLOT, year) %>%
  # Plots that have more than one observation per year
  filter(n() > 1) 

So, to sum up what I have done:

  • Plots where there is at least a year with more than one observation, and where those observations have a different EUNIS assigned: Plots REMOVED from the data
  • Plots where there is more than one observation, but observations have the same EUNIS assigned: kept in the data. Merged so that there is only one row per year. Info about the different dates (when different) is kept in columns date_obs_1 - date_obs_40, and info about the different PlotObservationID is kept in the columns PlotObservationID_obs_1 - PlotObservationID_obs_40.

Save to clean data

Save clean file for analyses (to be updated continuously due to updates in ReSurvey database and updates on RS data).

write_tsv(db_resurv_RS_short_PLOT,
          here("data", "clean","db_resurv_RS_short_PLOT_20250523.csv"))

Distributions all bioregions

# Define a function to create histograms
plot_histogram <- function(data, x_var, x_label) {
  ggplot(data %>%
           filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
         aes(x = !!sym(x_var))) +
    geom_histogram(color = "black", fill = "white") +
    labs(x = x_label, y = "Frequency") +
    theme_bw()
}
# Define a function to create plots with violin + boxplot + points
distr_plot <- function(data, y_vars, y_labels) {
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNIS level 1") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip()
    
    print(p)
  }
}

NDVI, NDMI, NDWI, SAVI and EVI

Ranges of min and max:

range(db_resurv_RS_short_PLOT$NDVI_max, na.rm = T)
[1] -0.0652840  0.9977221
range(db_resurv_RS_short_PLOT$NDMI_max, na.rm = T)
[1] -0.1656801  0.9996278
range(db_resurv_RS_short_PLOT$NDWI_max, na.rm = T)
[1] -0.9977221  0.9978094
range(db_resurv_RS_short_PLOT$SAVI_max, na.rm = T)
[1] -0.04495294  0.86572465
range(db_resurv_RS_short_PLOT$EVI_max, na.rm = T)
[1] -3.444331e-02  2.251800e+13
range(db_resurv_RS_short_PLOT$NDVI_min, na.rm = T)
[1] -0.9963834  0.9977221
range(db_resurv_RS_short_PLOT$NDMI_min, na.rm = T)
[1] -0.9988908  0.9995986
range(db_resurv_RS_short_PLOT$NDWI_min, na.rm = T)
[1] -0.99772209 -0.03415906
range(db_resurv_RS_short_PLOT$SAVI_min, na.rm = T)
[1] -0.5697995  0.7867734
range(db_resurv_RS_short_PLOT$EVI_min, na.rm = T)
[1] -43.78472   1.65059

Histograms to check that max and min values are ok:

plot_histogram(db_resurv_RS_short_PLOT, "NDVI_max", "NDVI max")

plot_histogram(db_resurv_RS_short_PLOT, "NDMI_max", "NDMI max")

plot_histogram(db_resurv_RS_short_PLOT, "NDWI_max", "NDWI max")

plot_histogram(db_resurv_RS_short_PLOT, "SAVI_max", "SAVI max")

plot_histogram(db_resurv_RS_short_PLOT %>% 
                 # Some values wrong!
                 filter(EVI_max <= 1), "EVI_max", "EVI max")

plot_histogram(db_resurv_RS_short_PLOT, "NDVI_min", "NDVI min")

plot_histogram(db_resurv_RS_short_PLOT, "NDMI_min", "NDMI min")

plot_histogram(db_resurv_RS_short_PLOT, "NDWI_min", "NDWI min")

plot_histogram(db_resurv_RS_short_PLOT, "SAVI_min", "SAVI min")

plot_histogram(db_resurv_RS_short_PLOT %>% 
                 # Some values wrong!
                 filter(EVI_min >= -1 & EVI_min <= 1), "EVI_min", "EVI min")

nrow(db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       filter(EVI_max > 1))
[1] 1594
db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q"))%>%
  filter(EVI_max > 1) %>%
  count(biogeo, unit)

So far, remove observations with EVI_max > 1 and EVI_min <-1 or > 1 from stuff using EVI values.

Distribution plots:

distr_plot(db_resurv_RS_short_PLOT,
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))

distr_plot(db_resurv_RS_short_PLOT,
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))

distr_plot(db_resurv_RS_short_PLOT,
           c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"), 
           c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))

distr_plot(db_resurv_RS_short_PLOT,
           c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"), 
           c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))

distr_plot(db_resurv_RS_short_PLOT %>%
             filter(EVI_max <= 1) %>%
             filter(EVI_min >= -1 & EVI_min <= 1),
           c("EVI_max", "EVI_p90", "EVI_min", "EVI_p10"), 
           c("EVI max", "EVI p90", "EVI min", "EVI p10"))

Some weird values –> Check.

CH

distr_plot(db_resurv_RS_short_PLOT, "canopy_height", "Canopy height (m)")

Show habitats with CH categories

ggplot(db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
         mutate(CH_cat =
                  factor(
                    case_when(canopy_height == 0 ~ "0 m",
                              canopy_height > 0 & canopy_height <= 1 ~ "0-1 m",
                              canopy_height > 1 & canopy_height <=2 ~ "1-2 m",
                              canopy_height > 2 & canopy_height <=5 ~ "2-5 m",
                              canopy_height > 5 & canopy_height <=8 ~ "5-8 m",
                              canopy_height > 8 ~ "> 8 m",
                              is.na(canopy_height) ~ NA_character_),
                    levels = c(
                      "0 m", "0-1 m", "1-2 m", "2-5 m", "5-8 m", "> 8 m"))),
       aes(x = EUNISa_1_descr, fill = CH_cat)) +
  geom_bar() + theme_bw() + coord_flip() +
  scale_y_continuous(labels = label_number()) +
  scale_fill_viridis_d(direction = -1) +
  labs(x = "EUNIS level 1", fill = "Canopy height") +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  theme(legend.position = c(0.8, 0.75),
        legend.direction = "vertical")

Stats per habitat type

db_resurv_RS_short_PLOT %>%
  # Keep only forests, grasslands, shrublands and wetlands
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  group_by(EUNISa_1_descr) %>%
  summarise(across(canopy_height, list(
    mean = mean,
    median = median,
    sd = sd,
    min = min,
    max = max
    ), na.rm = TRUE))

Phenology

Calculate metrics

db_resurv_RS_short_PLOT <- db_resurv_RS_short_PLOT %>%
  mutate(
    # Difference NDVI between Peak and SOS
    diff_Peak_SOS = NDVI_at_Peak - NDVI_at_SOS,
    # Difference NDVI between Peak and EOS
    diff_Peak_EOS = NDVI_at_Peak - NDVI_at_EOS)

Histograms phenology measures

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(SOS_DOY, Peak_DOY, EOS_DOY), names_to = "name",
                      values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(NDVI_at_SOS, NDVI_at_Peak, NDVI_at_EOS),
                      names_to = "name", values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(diff_Peak_SOS, diff_Peak_EOS),
                      names_to = "name", values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

ggplot(data = db_resurv_RS_short_PLOT %>%
         # Keep only forests, grasslands, shrublands and wetlands
         filter(EUNISa_1 %in% c("T", "R", "S", "Q") & S2_phen_data == T) %>%
         pivot_longer(cols = c(Season_Length),
                      names_to = "name", values_to = "value"),
       aes(x = value)) +
  geom_histogram(fill = "white", color = "black") +
  facet_grid(biogeo ~ name, scales = "free_y") +
  theme_bw()

Distributions

distr_plot(db_resurv_RS_short_PLOT,
           c("SOS_DOY","Peak_DOY", "EOS_DOY",
             "NDVI_at_SOS", "NDVI_at_Peak", "NDVI_at_EOS",
             "diff_Peak_SOS","diff_Peak_EOS", "Season_Length"),
           c("SOS DOY", "Peak DOY", "EOS DOY",
             "NDVI at SOS", "NDVI at Peak", "NDVI at EOS",
             "Difference Peak-SOS", "Difference Peak-EOS", "Season Length"))

Distributions per bioregion

# Define a function to create plots with violin + boxplot + points
distr_plot_biogeo <- function(data, y_vars, y_labels) {
  plots <- list()
  
  for (i in seq_along(y_vars)) {
    y_var <- y_vars[[i]]
    y_label <- y_labels[[i]]
    
    p <- ggplot(data = data %>%
                  filter(EUNISa_1 %in% c("T", "R", "S", "Q")),
                aes(x = EUNISa_1_descr, y = !!sym(y_var), fill = EUNISa_1_descr)) +
      geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8) +
      geom_point(aes(y = !!sym(y_var), color = EUNISa_1_descr),
                 position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
      geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
      stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
      stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1,
                                                     label = length(x)),
                   geom = "text", aes(label = ..label..), vjust = 0.5) +
      labs(y = y_label, x = "EUNISa_1_descr") +
      scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
      guides(fill = FALSE, color = FALSE) +
      theme_bw() + coord_flip() + facet_wrap(~ biogeo)
    
    plots[[y_var]] <- p
  }
  
  return(plots)
}

NDVI, NDMI, NDWI, SAVI and EVI

Distribution plots:

distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))
$NDVI_max

$NDVI_p90

$NDVI_min

$NDVI_p10

distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))
$NDMI_max

$NDMI_p90

$NDMI_min

$NDMI_p10

distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
           c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"), 
           c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))
$NDWI_max

$NDWI_p90

$NDWI_min

$NDWI_p10

distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
           c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"), 
           c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))
$SAVI_max

$SAVI_p90

$SAVI_min

$SAVI_p10

distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)) %>%
             filter(EVI_max <= 1) %>%
             filter(EVI_min >= -1 & EVI_min <= 1),
           c("EVI_max", "EVI_p90", "EVI_min", "EVI_p10"), 
           c("EVI max", "EVI p90", "EVI min", "EVI p10"))
$EVI_max

$EVI_p90

$EVI_min

$EVI_p10

CH

distr_plot_biogeo(db_resurv_RS_short_PLOT, "canopy_height", "Canopy height (m)")
$canopy_height

In this plot, those with biogeo = NA are those that do not have S2 or Landsat data (and thus biogeo has not been assigned), but have CH data. We should later assign a biogeo based on location.

Phenology

distr_plot_biogeo(db_resurv_RS_short_PLOT %>% filter(!is.na(biogeo)),
                  c("SOS_DOY", "Peak_DOY", "EOS_DOY",
                    "NDVI_at_SOS", "NDVI_at_Peak", "NDVI_at_EOS",
                    "diff_Peak_SOS", "diff_Peak_EOS", "Season_Length"),
                  c("SOS DOY", "Peak DOY", "EOS DOY",
                    "NDVI at SOS", "NDVI at Peak", "NDVI at EOS",
                    "Difference Peak-SOS", "Difference Peak-EOS",
                    "Season Length"))
$SOS_DOY

$Peak_DOY

$EOS_DOY

$NDVI_at_SOS

$NDVI_at_Peak

$NDVI_at_EOS

$diff_Peak_SOS

$diff_Peak_EOS

$Season_Length

BOR missing because there is no phenology info for EUNISa_1 %in% c(“T”, “R”, “S”, “Q”).

HERE: Verify SOS-Peak_EOS ODY

ERRORS! Bea is checking this:

db_resurv_RS_short_PLOT %>% filter(SOS_DOY > Peak_DOY)
db_resurv_RS_short_PLOT %>% filter(Peak_DOY > EOS_DOY)
db_resurv_RS_short_PLOT %>% filter(SOS_DOY > EOS_DOY)
db_resurv_RS_short_PLOT %>% filter(NDVI_at_Peak < NDVI_at_SOS)
db_resurv_RS_short_PLOT %>% filter(NDVI_at_Peak < NDVI_at_EOS)
db_resurv_RS_short_PLOT %>% filter(NDVI_at_Peak < NDVI_max)

Including PLOT (USE LATER?)

Summarize variables by plot:

db_resurv_RS_short_PLOT_summ <- db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  filter(S2_data == T | Landsat_data == T) %>%
  group_by(PLOT) %>%
  summarize(EUNIS1 = if_else(n_distinct(EUNISa_1) > 1, "Change",
                             unique(EUNISa_1)[1]),
            count = n(),
            across(starts_with("NDVI"), list(mean = mean, sd = sd),
                   .names = "{col}_{fn}"))

Maybe use later because now many plots have only one observation, probably because some Landsat data is missing?

First validation

For T, R, S, Q habitats.

Define a set of rules for a first validation of ALL ReSurvey data. We can call these “Expert-based” rules.

Number of observations in ReSurvey from the habitats of interest:

nrow(db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q")))
[1] 181062

Number of observations in ReSurvey from the habitats of interest and with all RS data:

nrow(db_resurv_RS_short_PLOT %>%
       filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
       filter(CH_data == T) %>%
       filter(S2_data == T | Landsat_data ==T) %>%
       filter(S2_phen_data == T))
[1] 5663
db_resurv_RS_short_PLOT_terrestrial <- db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q"))

Define rules

Create column for first validation based on different indicators, where “wrong” is noted when the validation rule is not met. Include EUNIS1 confusions.

db_resurv_RS_short_PLOT_terrestrial %>% count(EUNISa_1, EUNIS1_conf_type)

Define rules:

db_resurv_RS_short_PLOT_terrestrial <-
  db_resurv_RS_short_PLOT_terrestrial %>%
  mutate(
    valid_1_NDWI = case_when(
      # Points that are basically water
      NDWI_max > 0.3 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_CH = case_when(
      # T points with low CH
      EUNISa_1 == "T" & canopy_height < 8 ~ "wrong",
      # S points with low CH
      EUNISa_1 =="S" & canopy_height < 5 ~ "wrong",
      # R & Q points with high CH
      EUNISa_1 %in% c("R", "Q") & canopy_height > 2 ~ "wrong",
      TRUE ~ NA_character_),
    valid_1_NDVI = case_when(
      # T points with low NDVI_max
      EUNISa_1 == "T" & NDVI_max < 0.6 ~ "wrong",
      # S-R-Q points with low NDVI_max
      EUNISa_1 %in% c("R", "S", "Q") & NDVI_max < 0.2 ~ "wrong",
      TRUE ~ NA_character_),
    # Count how many validation rules are not met
    valid_1_count = rowSums(across(c(valid_1_NDWI, valid_1_CH, valid_1_NDVI), 
                             ~ . == "wrong"), na.rm = TRUE),
    # Points where at least 1 rule not met
    valid_1 = if_else(valid_1_count > 0, "At least 1 rule broken",
                      "No rules broken so far")
    )

Plots first validation

ggplot(db_resurv_RS_short_PLOT_terrestrial%>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           valid_1_count == 2 &
             valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
           valid_1_count == 3 ~ "NDWI + NDVI + CH",
           TRUE ~ NA_character_
         )), 
       aes(x = valid_1_count, fill = rules_broken)) +
  geom_bar() + labs(x = "Number of broken rules")

db_resurv_RS_short_PLOT_terrestrial %>%
         mutate(rules_broken = case_when(
           valid_1_count == 1 & valid_1_NDWI == "wrong" ~ "NDWI",
           valid_1_count == 1 & valid_1_NDVI == "wrong" ~ "NDVI",
           valid_1_count == 1 & valid_1_CH == "wrong" ~ "CH",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_NDVI == "wrong"~ "NDWI + NDVI",
           valid_1_count == 2 &
             valid_1_NDWI == "wrong" & valid_1_CH == "wrong"~ "NDWI + CH",
           valid_1_count == 2 &
             valid_1_NDVI == "wrong" & valid_1_CH == "wrong"~ "NDVI + CH",
           valid_1_count == 3 ~ "NDWI + NDVI + CH",
           TRUE ~ NA_character_
         )) %>%
  count(rules_broken, EUNIS1_conf_type)

Proportion of observations not validated (so far):

nrow(db_resurv_RS_short_PLOT_terrestrial %>% filter(valid_1_count > 0))/
  nrow(db_resurv_RS_short_PLOT_terrestrial)
[1] 0.1939004

But be aware that there are still MANY missing RS data.

ggplot(db_resurv_RS_short_PLOT_terrestrial %>%
         mutate(diff_GPS = if_else(
           `Location method` != "Location with differential GPS" |
             is.na(`Location method`), "no", "yes")), 
       aes(x = diff_GPS, fill = valid_1)) +
  geom_bar() + labs(x = "Differential GPS")

ggplot(db_resurv_RS_short_PLOT_terrestrial %>%
         mutate(GPS = case_when(
           `Location method` == "Location with differential GPS" ~ "yes",
           `Location method` == "Location with GPS" ~ "yes",
           is.na(`Location method`) ~ "no",
           TRUE ~ "no"
         )), 
       aes(x = GPS, fill = valid_1)) +
  geom_bar() + labs(x = "GPS")

Points with any rule broken and confusion between EUNIS:

nrow(db_resurv_RS_short_PLOT_terrestrial %>%
       filter(EUNIS1_conf == T & valid_1_count > 0))
[1] 10

Convert to shp to look at these in GIS:

# st_write(db_resurv_RS_short_PLOT_terrestrial %>%
#            filter(EUNIS1_conf == T & valid_1_count > 0) %>%
#            st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
#          "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_EUNIS_conf.shp")

Checked and yes

How many points with differential GPS that have at least 1 rule broken?

nrow(db_resurv_RS_short_PLOT_terrestrial %>%
  filter(`Location method` == "Location with differential GPS" &
           valid_1 == "At least 1 rule broken"))
[1] 1191

Convert to shp to look at these in GIS:

# st_write(db_resurv_RS_short_PLOT_terrestrial %>%
#            filter(`Location method` == "Location with differential GPS" &
#                     valid_1 == "At least 1 rule broken") %>%
#            st_as_sf(coords = c("Lon_updated", "Lat_updated"), crs = 4326),
#          "C:/GIS/MOTIVATE/shapefiles/resurv_not_val_diff_GPS.shp")

Maps

Points GPS

# Load world boundaries
world <- ne_countries(scale = "medium", returnclass = "sf")

# Calculate the extent of the points
points_GPS_extent <- db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  filter(S2_data == T | Landsat_data == T ) %>%
  filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS") %>%
  summarise(lon_min = min(Lon_updated, na.rm = TRUE),
            lon_max = max(Lon_updated, na.rm = TRUE),
            lat_min = min(Lat_updated, na.rm = TRUE),
            lat_max = max(Lat_updated, na.rm = TRUE))

# Add padding to the extent (adjust as needed)
padding <- 2  # Adjust padding to your preference
x_limits <- c(points_GPS_extent$lon_min - padding,
              points_GPS_extent$lon_max + padding)
y_limits <- c(points_GPS_extent$lat_min - padding,
              points_GPS_extent$lat_max + padding)

# Create the zoomed map
ggplot() +
  geom_sf(data = world, fill = "lightblue", color = "gray") +
  geom_point(data = db_resurv_RS_short_PLOT %>%
               filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
               filter(S2_data == T | Landsat_data == T ) %>%
               filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS"),
             aes(x = Lon_updated, y = Lat_updated, color = EUNISa_1),
             size = 1) +
  coord_sf(xlim = x_limits, ylim = y_limits) +
  theme_minimal()

Number of GPS points by Country:

db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  filter(S2_data == T | Landsat_data == T ) %>%
  filter(`Location method` == "Location with differential GPS" |
           `Location method` == "Location with GPS") %>%
  count(Country)

Points ReSurvey

# Calculate the extent of the points
points_resurvey_extent <- db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  filter(S2_data == T | Landsat_data == T ) %>%
  summarise(lon_min = min(Lon_updated, na.rm = TRUE),
            lon_max = max(Lon_updated, na.rm = TRUE),
            lat_min = min(Lat_updated, na.rm = TRUE),
            lat_max = max(Lat_updated, na.rm = TRUE))

# Add padding to the extent (adjust as needed)
padding <- 2  # Adjust padding to your preference
x_limits <- c(points_resurvey_extent$lon_min - padding,
              points_resurvey_extent$lon_max + padding)
y_limits <- c(points_resurvey_extent$lat_min - padding,
              points_resurvey_extent$lat_max + padding)

# Create the zoomed map
ggplot() +
  geom_sf(data = world, fill = "lightblue", color = "gray") +
  geom_point(data = db_resurv_RS_short_PLOT %>%
               filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
               filter(S2_data == T | Landsat_data == T ),
             aes(x = Lon_updated, y = Lat_updated, color = EUNISa_1),
             size = 1) +
  coord_sf(xlim = x_limits, ylim = y_limits) +
  theme_minimal()

Number of ReSurvey points by Country:

db_resurv_RS_short_PLOT %>%
  filter(EUNISa_1 %in% c("T", "R", "S", "Q")) %>%
  filter(S2_data == T | Landsat_data == T ) %>%
  count(Country)

Distributions from GPS points without rules broken so far

Create tibble with differential GPS points without rules broken so far:

all_GPS_valid <- db_resurv_RS_short_PLOT_terrestrial %>%
  filter((`Location method` == "Location with differential GPS" | 
            `Location method` == "Location with GPS" ) &
           valid_1 == "No rules broken so far") 

NDVI, NDMI, NDWI, SAVI and EVI

distr_plot(all_GPS_valid,
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))

distr_plot(all_GPS_valid,
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))

distr_plot(all_GPS_valid,
           c("NDWI_max", "NDWI_p90", "NDWI_min", "NDWI_p10"), 
           c("NDWI max", "NDWI p90", "NDWI min", "NDWI p10"))

distr_plot(all_GPS_valid,
           c("SAVI_max", "SAVI_p90", "SAVI_min", "SAVI_p10"), 
           c("SAVI max", "SAVI p90", "SAVI min", "SAVI p10"))

distr_plot(all_GPS_valid %>%
             filter(EVI_max <= 1) %>%
             filter(EVI_min >= -1 & EVI_min <= 1),
           c("EVI_max", "EVI_p90", "EVI_min", "EVI_p10"), 
           c("EVI max", "EVI p90", "EVI min", "EVI p10"))

CH

distr_plot(all_GPS_valid, "canopy_height", "Canopy height (m)")

Phenology

distr_plot(all_GPS_valid,
           c("SOS_DOY","Peak_DOY", "EOS_DOY",
             "NDVI_at_SOS", "NDVI_at_Peak", "NDVI_at_EOS",
             "diff_Peak_SOS","diff_Peak_EOS", "Season_Length"),
           c("SOS DOY", "Peak DOY", "EOS DOY",
             "NDVI at SOS", "NDVI at Peak", "NDVI at EOS",
             "Difference Peak-SOS", "Difference Peak-EOS", "Season Length"))

GPS valid points above p20 of NDVI_max and NDMI_min for each habitat

HERE!

Chosen NDVI_min because it was important in RF models, but let’s see with new data!

percentiles_all_GPS <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(percentile_20_NDVI_max = quantile(NDVI_max, probs = 0.20, na.rm = T),
            percentile_20_NDMI_min = quantile(NDMI_min, probs = 0.20, na.rm = T))

all_GPS_valid <- all_GPS_valid %>%
  left_join(percentiles_all_GPS, by = "EUNISa_1") %>%
  mutate(category_NDVI_max = case_when(
    NDVI_max < percentile_20_NDVI_max ~ "below_20th",
    NDVI_max >= percentile_20_NDVI_max ~ "above_20th"),
  category_NDMI_min = case_when(
    NDMI_min < percentile_20_NDMI_min ~ "below_20th",
    NDMI_min >= percentile_20_NDMI_min ~ "above_20th"))

ggplot(data = all_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDVI_max)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDVI_max),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDVI max", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()


ggplot(data = all_GPS_valid,
       aes(x = EUNISa_1_descr, y = NDMI_min)) +
  geom_flat_violin(position = position_nudge(x = 0.2, y = 0), alpha = 0.8,
                   fill = "lightblue") +
  geom_point(aes(color = category_NDMI_min),
             position = position_jitter(width = 0.15), size = 1, alpha = 0.25) +
  geom_boxplot(width = 0.2, outlier.shape = NA, alpha = 0.5) +
  stat_summary(fun.y = mean, geom = "point", shape = 20, size = 1) +
  stat_summary(fun.data = function(x) data.frame(y = max(x) + 0.1, label = length(x)),
               geom = "text", aes(label = ..label..), vjust = 0.5) +
  labs(y = "NDMI min", x = "EUNIS level 1") +
  guides(fill = FALSE, color = FALSE) +
  scale_x_discrete(labels = function(x) str_wrap(x, width = 15)) +
  scale_color_manual(values = c("below_20th" = "grey", "above_20th" = "lightblue")) +
  theme_bw() + coord_flip()

RF models

Using the conditional inference version of random forest (cforest in package party). Suggested if the data are highly correlated. Cforest is more stable in deriving variable importance values in the presence of highly correlated variables, thus providing better accuracy in calculating variable importance (ref below).

Hothorn, T., Hornik, K. and Zeileis, A. (2006) Unbiased Recursive Portioning: A Conditional Inference Framework. Journal of Computational and Graphical Statistics, 15, 651- 674. http://dx.doi.org/10.1198/106186006X133933

All GPS points

filtered_data0 <- all_GPS_valid %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices0 <- sample(1:nrow(filtered_data0), 0.7 * nrow(filtered_data0))
train_data0 <- filtered_data0[train_indices0, ]
test_data0 <- filtered_data0[-train_indices0, ]

Number of points per category for filtered data:

filtered_data0 %>% count(EUNISa_1)
rf_cforest0 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data0,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest0 <- predict(rf_cforest0, newdata = test_data0,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest0, test_data0$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction   Q   R   S   T
         Q   0   0   0   0
         R  23 716   0   0
         S   0   0   0   0
         T   0   0   1  28

Overall Statistics
                                          
               Accuracy : 0.9688          
                 95% CI : (0.9539, 0.9799)
    No Information Rate : 0.9323          
    P-Value [Acc > NIR] : 6.673e-06       
                                          
                  Kappa : 0.6922          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity           0.00000   1.0000 0.000000  1.00000
Specificity           1.00000   0.5577 1.000000  0.99865
Pos Pred Value            NaN   0.9689      NaN  0.96552
Neg Pred Value        0.97005   1.0000 0.998698  1.00000
Prevalence            0.02995   0.9323 0.001302  0.03646
Detection Rate        0.00000   0.9323 0.000000  0.03646
Detection Prevalence  0.00000   0.9622 0.000000  0.03776
Balanced Accuracy     0.50000   0.7788 0.500000  0.99932
varimp_rf_cforest0 <- party::varimp(rf_cforest0, conditional = F) 

Variable Importance Plot

varimp_rf_cforest0_df <- data.frame(Variable = names(varimp_rf_cforest0),
                                    Importance = varimp_rf_cforest0)
ggplot(varimp_rf_cforest0_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest0, newdata = test_data0, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data0$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc0 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc0

REVISE FROM HERE: All GPS points above p20

Filter the data to get only GPS-points above p20 of NDVI_max and NDMI_min.

all_GPS_valid <- all_GPS_valid %>%
  select(-percentile_20_NDVI_max, -percentile_20_NDMI_min)
percentiles <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    percentile_20_NDVI_max = quantile(NDVI_max, 0.20, na.rm = T),
    percentile_20_NDMI_min = quantile(NDMI_min, 0.20, na.rm = T),
    percentile_80_NDVI_max = quantile(NDVI_max, 0.80, na.rm = T),
    percentile_80_NDMI_min = quantile(NDMI_min, 0.80, na.rm = T)
    )

# Join the percentiles back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(percentiles, by = "EUNISa_1")

# Filter rows above the 20th percentile for both variables for each category of EUNISa_1
filtered_data1 <- all_GPS_valid %>%
  filter(
    NDVI_max >= percentile_20_NDVI_max & NDMI_min >= percentile_20_NDMI_min
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices1 <- sample(1:nrow(filtered_data1), 0.7 * nrow(filtered_data1))
train_data1 <- filtered_data1[train_indices1, ]
test_data1 <- filtered_data1[-train_indices1, ]

Number of points per category for filtered data:

filtered_data1 %>% count(EUNISa_1)

Investigate package ggparty (e.g. autoplot function, and more).

TO-DO: Choose the hyperparameter mtry based on the square root of the number of predictor variables (Hastie et al., 2009)-

Hastie, T., Tibshirani, R., & Friedman, J. (2009). The elements of statistical learning: Data mining, inference, and prediction. Springer Science & Business Media.

Maybe TO_DO: We variated ntree from 50 to 800 in steps of 50, leaving mtry constant at 2. Tis parameter variation showed that ntree=500 was optimal, while higher ntree led to no further model improvement (Supplementary Fig. S10). Subsequently, the hyperparameter mtry was varied from 2 to 8 with constant ntree=500. Here, mtry=3 led to the best results in almost all cases (Supplementary Fig. S11). Consequently, we chose ntree=500 and mtry=3 for our main analysis across all study sites.

rf_cforest1 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data1,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest1 <- predict(rf_cforest1, newdata = test_data1,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest1, test_data1$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  632  325    0    0
         R  247 1491    0    0
         S    0    0   45    3
         T    0    0    7   77

Overall Statistics
                                          
               Accuracy : 0.7941          
                 95% CI : (0.7787, 0.8089)
    No Information Rate : 0.6424          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.5872          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.7190   0.8210  0.86538  0.96250
Specificity            0.8332   0.7557  0.99892  0.99745
Pos Pred Value         0.6604   0.8579  0.93750  0.91667
Neg Pred Value         0.8679   0.7016  0.99748  0.99891
Prevalence             0.3109   0.6424  0.01839  0.02830
Detection Rate         0.2236   0.5274  0.01592  0.02724
Detection Prevalence   0.3385   0.6148  0.01698  0.02971
Balanced Accuracy      0.7761   0.7884  0.93215  0.97998

SurrogateTree –> does not work

varimp_rf_cforest1 <- party::varimp(rf_cforest1, conditional = F) 

Variable Importance Plot

varimp_rf_cforest1_df <- data.frame(Variable = names(varimp_rf_cforest1),
                                    Importance = varimp_rf_cforest1)
ggplot(varimp_rf_cforest1_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

Tree Visualization

# Create a single conditional inference tree using ctree
single_tree1 <- ctree(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max + NDMI_min +
                       NDWI_max + NDWI_min + EVI_max + EVI_min + SAVI_max +
                       SAVI_min + canopy_height,
                     data = train_data1)

# Plot the single tree using
autoplot(single_tree1)

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc1 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc1

All GPS points within IQ range

Filter the data to get only GPS-points within IQ range of NDVI_max and NDMI_min.

IQ_ranges <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    Q1_NDVI_max = quantile(NDVI_max, 0.25, na.rm = T),
    Q1_NDMI_min = quantile(NDMI_min, 0.25, na.rm = T),
    Q3_NDVI_max = quantile(NDVI_max, 0.75, na.rm = T),
    Q3_NDMI_min = quantile(NDMI_min, 0.75, na.rm = T),
    IQR_NDVI_max = IQR(NDVI_max, na.rm = TRUE),
    IQR_NDMI_min = IQR(NDMI_min, na.rm = TRUE)
    )

# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(IQ_ranges, by = "EUNISa_1")

# Filter rows within the IQR range for both variables
filtered_data2 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= Q1_NDVI_max & NDVI_max <= Q3_NDVI_max) &
    (NDMI_min >= Q1_NDMI_min & NDMI_min <= Q3_NDMI_min)
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices2 <- sample(1:nrow(filtered_data2), 0.7 * nrow(filtered_data2))
train_data2 <- filtered_data2[train_indices2, ]
test_data2 <- filtered_data2[-train_indices2, ]

Number of points per category for filtered data:

filtered_data2 %>% count(EUNISa_1)
rf_cforest2 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data2,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest2 <- predict(rf_cforest2, newdata = test_data2,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest2, test_data2$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction   Q   R   S   T
         Q 325   6   0   0
         R  45 870   0   0
         S   0   0  14   2
         T   0   0   2  23

Overall Statistics
                                          
               Accuracy : 0.9573          
                 95% CI : (0.9447, 0.9676)
    No Information Rate : 0.6807          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.9032          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.8784   0.9932  0.87500  0.92000
Specificity            0.9935   0.8905  0.99843  0.99842
Pos Pred Value         0.9819   0.9508  0.87500  0.92000
Neg Pred Value         0.9529   0.9839  0.99843  0.99842
Prevalence             0.2875   0.6807  0.01243  0.01943
Detection Rate         0.2525   0.6760  0.01088  0.01787
Detection Prevalence   0.2572   0.7110  0.01243  0.01943
Balanced Accuracy      0.9359   0.9418  0.93671  0.95921
varimp_rf_cforest2 <- party::varimp(rf_cforest2, conditional = F) 

Variable Importance Plot

varimp_rf_cforest2_df <- data.frame(Variable = names(varimp_rf_cforest2),
                                    Importance = varimp_rf_cforest2)
ggplot(varimp_rf_cforest2_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc2 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc2

All GPS points within 1.5 * IQ range

Filter the data to get only GPS-points within 1.5 * IQ range of NDVI_max and NDMI_min.

# Filter rows within the 1.5 * IQR range for both variables
filtered_data3 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= (Q1_NDVI_max - 1.5 * IQR_NDVI_max) & NDVI_max <= (Q3_NDVI_max + 1.5 * IQR_NDVI_max)) &
      (NDMI_min >= (Q1_NDMI_min - 1.5 * IQR_NDMI_min) & NDMI_min <= (Q3_NDMI_min + 1.5 * IQR_NDMI_min))
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices3 <- sample(1:nrow(filtered_data3), 0.7 * nrow(filtered_data3))
train_data3 <- filtered_data3[train_indices3, ]
test_data3 <- filtered_data3[-train_indices3, ]

Number of points per category for filtered data:

filtered_data3 %>% count(EUNISa_1)
rf_cforest3 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data3,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest3 <- predict(rf_cforest3, newdata = test_data3,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest3, test_data3$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  660  355    0    0
         R  552 2132    0    0
         S    0    0   48    7
         T    0    0   21   96

Overall Statistics
                                          
               Accuracy : 0.7585          
                 95% CI : (0.7447, 0.7719)
    No Information Rate : 0.6425          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.4876          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.5446   0.8573  0.69565  0.93204
Specificity            0.8665   0.6012  0.99816  0.99443
Pos Pred Value         0.6502   0.7943  0.87273  0.82051
Neg Pred Value         0.8067   0.7009  0.99450  0.99814
Prevalence             0.3131   0.6425  0.01782  0.02661
Detection Rate         0.1705   0.5508  0.01240  0.02480
Detection Prevalence   0.2622   0.6934  0.01421  0.03022
Balanced Accuracy      0.7055   0.7292  0.84691  0.96323
varimp_rf_cforest3 <- party::varimp(rf_cforest3, conditional = F) 

Variable Importance Plot

varimp_rf_cforest3_df <- data.frame(Variable = names(varimp_rf_cforest3),
                                    Importance = varimp_rf_cforest3)
ggplot(varimp_rf_cforest3_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc3 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc3

All GPS points within mean +/- SD

Filter the data to get only GPS-points within mean +/- SD of NDVI_max and NDMI_min.

mean_sd <- all_GPS_valid %>%
  group_by(EUNISa_1) %>%
  summarize(
    mean_NDVI_max = mean(all_GPS_valid$NDVI_max, na.rm = T),
    mean_NDMI_min = mean(all_GPS_valid$NDMI_min, na.rm = T),
    sd_NDVI_max = sd(all_GPS_valid$NDVI_max, na.rm = T),
    sd_NDMI_min = sd(all_GPS_valid$NDMI_min, na.rm = T)
    )

# Join the IQ ranges back to the original data
all_GPS_valid <- all_GPS_valid %>%
  left_join(mean_sd, by = "EUNISa_1")

# Filter rows within the specified range for both variables
filtered_data4 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= (mean_NDVI_max - sd_NDVI_max) & NDVI_max <= (mean_NDVI_max + sd_NDVI_max)) &
      (NDMI_min >= (mean_NDMI_min - sd_NDMI_min) & NDMI_min <= (mean_NDMI_min + sd_NDMI_min))
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices4 <- sample(1:nrow(filtered_data4), 0.7 * nrow(filtered_data4))
train_data4 <- filtered_data4[train_indices4, ]
test_data4 <- filtered_data4[-train_indices4, ]

Number of points per category for filtered data:

filtered_data4 %>% count(EUNISa_1)
rf_cforest4 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data4,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest4 <- predict(rf_cforest4, newdata = test_data4,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest4, test_data4$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  281  182    0    0
         R  485 1233    0    0
         S    0    0   27    9
         T    0    0   17   60

Overall Statistics
                                          
               Accuracy : 0.6979          
                 95% CI : (0.6787, 0.7167)
    No Information Rate : 0.6168          
    P-Value [Acc > NIR] : 2.873e-16       
                                          
                  Kappa : 0.3564          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.3668   0.8714  0.61364  0.86957
Specificity            0.8809   0.4482  0.99600  0.99236
Pos Pred Value         0.6069   0.7177  0.75000  0.77922
Neg Pred Value         0.7351   0.6840  0.99247  0.99594
Prevalence             0.3339   0.6168  0.01918  0.03008
Detection Rate         0.1225   0.5375  0.01177  0.02616
Detection Prevalence   0.2018   0.7489  0.01569  0.03357
Balanced Accuracy      0.6239   0.6598  0.80482  0.93096
varimp_rf_cforest4 <- party::varimp(rf_cforest4, conditional = F) 

Variable Importance Plot

varimp_rf_cforest4_df <- data.frame(Variable = names(varimp_rf_cforest4),
                                    Importance = varimp_rf_cforest4)
ggplot(varimp_rf_cforest4_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc4 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc4

All GPS points above p20 and below p80

Filter the data to get only GPS-points above p20 and below p80 of NDVI_max and NDMI_min.

# Filter rows above the 20th percentile and below the 80th percentile for both variables
filtered_data5 <- all_GPS_valid %>%
  filter(
    (NDVI_max >= percentile_20_NDVI_max & NDVI_max <= percentile_80_NDVI_max) &
    (NDMI_min >= percentile_20_NDMI_min & NDMI_min <= percentile_80_NDMI_min)
    ) %>%
  filter(!is.na(NDVI_max) & !is.na(NDMI_max) & !is.na(NDWI_max) &
           !is.na(SAVI_max) & !is.na(EVI_max) & !is.na(NDVI_min) &
           !is.na(NDMI_min) & !is.na(NDWI_min) & !is.na(SAVI_min) &
           !is.na(EVI_min)) %>%
  mutate(EUNISa_1 = as.factor(EUNISa_1)) %>%
  filter(EVI_max <= 1 & EVI_min >= -1)

Split into training and test data sets.

train_indices5 <- sample(1:nrow(filtered_data5), 0.7 * nrow(filtered_data5))
train_data5 <- filtered_data5[train_indices5, ]
test_data5 <- filtered_data5[-train_indices5, ]

Number of points per category for filtered data:

filtered_data5 %>% count(EUNISa_1)
rf_cforest5 <- party::cforest(EUNISa_1 ~ NDVI_max + NDVI_min + NDMI_max +
                                NDMI_min + NDWI_max + NDWI_min + EVI_max +
                                EVI_min + SAVI_max + SAVI_min + canopy_height, 
                              data = train_data5,
                              controls = cforest_control(
                                mtry = 3,
                                # mtry = sqrt(11)
                                # Default mtry = 5
                                # Bagging: mtry = NULL
                                # or = number of input variables
                                ntree = 500) # Default, try increasing
                              ) 
predictions_rf_cforest5 <- predict(rf_cforest5, newdata = test_data5,
                                   OOB = TRUE, type = "response")

Confusion matrix:

confusionMatrix(predictions_rf_cforest5, test_data5$EUNISa_1)
Confusion Matrix and Statistics

          Reference
Prediction    Q    R    S    T
         Q  389   38    0    0
         R  139 1120    0    0
         S    0    0   17    3
         T    0    0    8   32

Overall Statistics
                                          
               Accuracy : 0.8923          
                 95% CI : (0.8768, 0.9065)
    No Information Rate : 0.6632          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.7592          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: Q Class: R Class: S Class: T
Sensitivity            0.7367   0.9672 0.680000  0.91429
Specificity            0.9688   0.7636 0.998257  0.99532
Pos Pred Value         0.9110   0.8896 0.850000  0.80000
Neg Pred Value         0.8946   0.9220 0.995365  0.99824
Prevalence             0.3024   0.6632 0.014318  0.02005
Detection Rate         0.2228   0.6415 0.009737  0.01833
Detection Prevalence   0.2446   0.7211 0.011455  0.02291
Balanced Accuracy      0.8528   0.8654 0.839128  0.95481
varimp_rf_cforest5 <- party::varimp(rf_cforest5, conditional = F) 

Variable Importance Plot

varimp_rf_cforest5_df <- data.frame(Variable = names(varimp_rf_cforest5),
                                    Importance = varimp_rf_cforest5)
ggplot(varimp_rf_cforest5_df,
       aes(x = reorder(Variable, Importance), y = Importance)) +
  geom_bar(stat = "identity", fill = "lightblue") +
  coord_flip() + theme_minimal() +
  labs(title = "Variable Importance", x = "Variables", y = "Importance")

ROC curves:

# Predict probabilities for each class
probabilities <- predict(rf_cforest1, newdata = test_data1, type = "prob")

# Step 1: Convert list of matrices to a proper data frame
prob_matrix <- t(sapply(probabilities, as.vector))
colnames(prob_matrix) <- c("Q", "R", "S", "T")  # Adjust if needed
prob_df <- as.data.frame(prob_matrix)

# Step 2: Prepare actual class labels
actual <- factor(test_data1$EUNISa_1, levels = c("Q", "R", "S", "T"))
classes <- levels(actual)

# Step 3: Binarize actual labels
actual_bin <- model.matrix(~ actual - 1)
colnames(actual_bin) <- gsub("actual", "", colnames(actual_bin))

# Step 4: Compute ROC data for each class with AUC in label
roc_data <- lapply(classes, function(class) {
  roc_obj <- roc(actual_bin[, class], prob_df[[class]])
  auc_val <- round(auc(roc_obj), 3)
  data.frame(
    FPR = rev(roc_obj$specificities),
    TPR = rev(roc_obj$sensitivities),
    Class = paste0(class, " (AUC = ", auc_val, ")")
  )
}) %>% bind_rows()
G3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
gG3;Setting levels: control = 0, case = 1
gG3;Setting direction: controls < cases
g
# Step 5: Plot ROC curves with ggplot2
roc5 <- ggplot(roc_data, aes(x = FPR, y = TPR, color = Class)) +
  geom_line(size = 1.2) +
  geom_abline(linetype = "dashed", color = "gray") +
  labs(
    title = "Multiclass ROC Curves with AUC",
    x = "False Positive Rate",
    y = "True Positive Rate",
    color = "Class (AUC)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")
roc5

HERE: Compare RF 1-5

Cordillera data

AlpineGrasslands_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrassland_Sentinel_Plot_Allyear_Allmetrics.csv")
Rows: 40 Columns: 69
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr  (9): system:index, Codigo, H�bitat, Localidad, Precisi�n, Subregion, UTM, s...
dbl (60): Altitude__, Altura_med, Altura_m�, Cobertur_1, Cobertur_2, Cobertura, ...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
AlpineGrasslands_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
Rows: 40 Columns: 34
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr  (8): system:index, Codigo, H�bitat, Localidad, Precisi�n, Subregion, UTM, .geo
dbl (26): Altitude__, Altura_med, Altura_m�, Cobertur_1, Cobertur_2, Cobertura, ...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
AlpineGrasslands_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/AlpineGrasslands/AlpineGrasslands_CanopyHeight_1m.csv")
Rows: 40 Columns: 27
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr  (8): system:index, Codigo, Hábitat, Localidad, Precisión, Subregion, UTM, .geo
dbl (19): Altitude__, Altura_med, Altura_má, Cobertur_1, Cobertur_2, Cobertura, ...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
VegetationTypes_indices <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Sentinel_Plot_AllYear_Allmetrics.csv")
Rows: 32 Columns: 54
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr   (8): system:index, EUNIS, ID, TYPE, Xo, Yo, source, .geo
dbl  (45): EVI_max, EVI_median, EVI_min, EVI_mode, EVI_p10, EVI_p90, EVI_stdDev,...
date  (1): DATE

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
VegetationTypes_phen <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_Phenology_SOS_EOS_Peak_NDVI_Amplitude.csv")
Rows: 32 Columns: 19
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr   (7): system:index, EUNIS, ID, TYPE, Xo, Yo, .geo
dbl  (11): EOS_DOY, NDVI_Amplitude, NDVI_at_EOS, NDVI_at_Peak, NDVI_at_SOS, Peak...
date  (1): DATE

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
VegetationTypes_CH <- read_csv(
  "C:/Data/MOTIVATE/Cordillera/VegetationTypes/VegetationTypes_CanopyHeight_1m.csv")
Rows: 32 Columns: 12
── Column specification ────────────────────────────────────────────────────────────
Delimiter: ","
chr  (7): system:index, EUNIS, ID, TYPE, Xo, Yo, .geo
dbl  (4): X, Y, canopy_height, evaluated
date (1): DATE

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
AlpineGrasslands <- AlpineGrasslands_indices %>%
  select(-`system:index`, -.geo, -Localidad) %>%
  rename(Hábitat = "H�bitat") %>% 
  full_join(AlpineGrasslands_phen  %>%
              select(-`system:index`, -.geo, -Localidad) %>%
              rename(Hábitat = "H�bitat")) %>%
  full_join(AlpineGrasslands_CH  %>%
              select(-`system:index`, -.geo, -Localidad)) %>%
  select(-Date__year, - `Precisi�n`) %>%
  mutate(DATE = ymd(DATE)) %>%
  rename(ID = "Releve_num") %>%
  mutate(ID = as.character(ID)) %>%
  mutate(layer = "AlpineGrasslands")
Joining with `by = join_by(Altitude__, Altura_med, `Altura_m�`, Cobertur_1,
Cobertur_2, Cobertura, Codigo, DATE, Gps_error, Hgtavg_hl, Hgtmax_hl, Hábitat,
Latitude, Longitude, Num_specie, Orientaci, Pendiente, `Precisi�n`, Releve_num,
Subregion, UTM, X, Y)`
Joining with `by = join_by(Altitude__, Altura_med, Cobertur_1, Cobertur_2,
Cobertura, Codigo, Gps_error, Hgtavg_hl, Hgtmax_hl, Hábitat, Latitude, Longitude,
Num_specie, Orientaci, Pendiente, Releve_num, Subregion, UTM, X, Y)`
VegetationTypes <- VegetationTypes_indices %>%
  select(-`system:index`, -.geo) %>%
  full_join(VegetationTypes_phen  %>%
              select(-`system:index`, -.geo)) %>%
  full_join(VegetationTypes_CH  %>%
              select(-`system:index`, -.geo)) %>%
  rename(Hábitat = "TYPE") %>%
  mutate(layer = "VegetationTypes")
Joining with `by = join_by(DATE, EUNIS, ID, TYPE, X, Xo, Y, Yo, evaluated)`
Joining with `by = join_by(DATE, EUNIS, ID, TYPE, X, Xo, Y, Yo, evaluated)`

Merge both datasets:

cordillera <- bind_rows(
  AlpineGrasslands %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer"),
  VegetationTypes %>% select(DATE, ID, starts_with("NDMI"),
                              starts_with("NDVI"), Hábitat, "EOS_DOY",
                              "Peak_DOY", "SOS_DOY", "Season_Length",
                              "canopy_height", "layer")
  ) %>%
  mutate(EUNISa_1 = case_when(
    Hábitat = str_detect(Hábitat, "Pastizal|Cervunal|grassland|meadow") ~ "R",
    Hábitat = str_detect(Hábitat, "forest") ~ "T",
    Hábitat = str_detect(Hábitat, "Scrub|scrub|Shrubland|shrubland|shrub|Heathland") ~ "S",
    Hábitat = str_detect(Hábitat, "Suelo|Scree|scree|cliff") ~ "U",
    Hábitat = is.na(Hábitat) ~ "R",
    TRUE ~ NA_character_),
    EUNISa_1_descr = case_when(
      EUNISa_1 == "R" ~ "Grasslands",
      EUNISa_1 == "T" ~ "Forests and other wooded land",
      EUNISa_1 == "S" ~ "Heathlands, scrub and tundra",
      EUNISa_1 == "U" ~ "Inland habitats with no or little soil")
    )

NDVI, NDMI

distr_plot(cordillera,
           c("NDVI_max", "NDVI_p90", "NDVI_min", "NDVI_p10"), 
           c("NDVI max", "NDVI p90", "NDVI min", "NDVI p10"))

distr_plot(cordillera,
           c("NDMI_max", "NDMI_p90", "NDMI_min", "NDMI_p10"), 
           c("NDMI max", "NDMI p90", "NDMI min", "NDMI p10"))

Session info

sessionInfo()
R version 4.5.0 (2025-04-11 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=Spanish_Spain.utf8  LC_CTYPE=Spanish_Spain.utf8   
[3] LC_MONETARY=Spanish_Spain.utf8 LC_NUMERIC=C                  
[5] LC_TIME=Spanish_Spain.utf8    

time zone: Europe/Madrid
tzcode source: internal

attached base packages:
[1] stats4    grid      stats     graphics  grDevices utils     datasets  methods  
[9] base     

other attached packages:
 [1] pROC_1.18.5          randomForest_4.7-1.2 moreparty_0.4       
 [4] caret_7.0-1          lattice_0.22-6       ggparty_1.0.0       
 [7] partykit_1.2-24      libcoin_1.0-10       party_1.3-18        
[10] strucchange_1.5-4    sandwich_3.1-1       zoo_1.8-14          
[13] modeltools_0.2-24    mvtnorm_1.3-3        ggeffects_2.2.1     
[16] car_3.1-3            carData_3.0-5        lmerTest_3.1-3      
[19] lme4_1.1-37          Matrix_1.7-3         dtplyr_1.3.1        
[22] rnaturalearth_1.0.1  sf_1.0-20            scales_1.4.0        
[25] readxl_1.4.5         gridExtra_2.3        here_1.0.1          
[28] lubridate_1.9.4      forcats_1.0.0        stringr_1.5.1       
[31] dplyr_1.1.4          purrr_1.0.4          readr_2.1.5         
[34] tidyr_1.3.1          tibble_3.2.1         ggplot2_3.5.2       
[37] tidyverse_2.0.0     

loaded via a namespace (and not attached):
  [1] RColorBrewer_1.1-3      rstudioapi_0.17.1       jsonlite_2.0.0         
  [4] magrittr_2.0.3          TH.data_1.1-3           farver_2.1.2           
  [7] nloptr_2.2.1            rmarkdown_2.29          vctrs_0.6.5            
 [10] minqa_1.2.8             terra_1.8-50            htmltools_0.5.8.1      
 [13] varImp_0.4              cellranger_1.1.0        Formula_1.2-5          
 [16] sass_0.4.10             parallelly_1.44.0       bslib_0.9.0            
 [19] KernSmooth_2.23-26      htmlwidgets_1.6.4       plyr_1.8.9             
 [22] cachem_1.1.0            mime_0.13               lifecycle_1.0.4        
 [25] iterators_1.0.14        pkgconfig_2.0.3         R6_2.6.1               
 [28] fastmap_1.2.0           shiny_1.10.0            rbibutils_2.3          
 [31] future_1.49.0           digest_0.6.37           numDeriv_2016.8-1.1    
 [34] rprojroot_2.0.4         labeling_0.4.3          timechange_0.3.0       
 [37] httr_1.4.7              abind_1.4-8             compiler_4.5.0         
 [40] proxy_0.4-27            bit64_4.6.0-1           withr_3.0.2            
 [43] backports_1.5.0         DBI_1.2.3               MASS_7.3-65            
 [46] lava_1.8.1              classInt_0.4-11         ModelMetrics_1.2.2.2   
 [49] tools_4.5.0             units_0.8-7             httpuv_1.6.16          
 [52] future.apply_1.11.3     nnet_7.3-20             rnaturalearthdata_1.0.0
 [55] glue_1.8.0              promises_1.3.2          nlme_3.1-168           
 [58] inum_1.0-5              checkmate_2.3.2         reshape2_1.4.4         
 [61] generics_0.1.4          recipes_1.3.0           gtable_0.3.6           
 [64] tzdb_0.5.0              class_7.3-23            data.table_1.17.2      
 [67] hms_1.1.3               utf8_1.2.5              coin_1.4-3             
 [70] foreach_1.5.2           pillar_1.10.2           vroom_1.6.5            
 [73] later_1.4.2             splines_4.5.0           bit_4.6.0              
 [76] survival_3.8-3          tidyselect_1.2.1        knitr_1.50             
 [79] reformulas_0.4.1        xfun_0.52               measures_0.3           
 [82] hardhat_1.4.1           timeDate_4041.110       matrixStats_1.5.0      
 [85] DT_0.33                 phosphoricons_0.2.1     stringi_1.8.7          
 [88] yaml_2.3.10             boot_1.3-31             shinyWidgets_0.9.0     
 [91] evaluate_1.0.3          codetools_0.2-20        cli_3.6.5              
 [94] rpart_4.1.24            xtable_1.8-4            Rdpack_2.6.4           
 [97] jquerylib_0.1.4         Rcpp_1.0.14             globals_0.18.0         
[100] parallel_4.5.0          rclipboard_0.2.1        gower_1.0.2            
[103] listenv_0.9.1           viridisLite_0.4.2       ipred_0.9-15           
[106] prodlim_2025.04.28      e1071_1.7-16            crayon_1.5.3           
[109] insight_1.2.0           rlang_1.1.6             multcomp_1.4-28        
LS0tDQp0aXRsZTogIlNjcmlwdCB0byB2YWxpZGF0ZSBwb2ludHMgaW4gUmVTdXJ2ZXkgZGF0YWJhc2UgdXNpbmcgUlMgZGF0YSINCnN1YnRpdGxlOiAiQWRkaW5nIEFUTF9CRU5FTFVYIGFuZCBDT05fTk9SRElDIFMyIGRhdGEsIHNvbWUgTGFuZHNhdCBkYXRhLCBhZGRpbmcgY2Fub3B5IGhlaWdodCBkYXRhIg0KYXV0aG9yOiAiQWxpY2lhIFZhbGTDqXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gRkFMU0UpDQpgYGANCg0KVGhpcyBSIHNjcmlwdCBpcyB1c2VkIHRvIHZhbGlkYXRlIHRoZSBwb2ludHMgaW4gdGhlIFJlU3VydmV5IGRhdGFiYXNlIHVzaW5nIFJTIGluZGljYXRvcnMgKE5EVkksIE5ETUksIGNhbm9weSBoZWlnaHQpLg0KDQojIExvYWQgbGlicmFyaWVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGhlcmUpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKQ0KbGlicmFyeShkdHBseXIpDQpsaWJyYXJ5KGxtZTQpDQpsaWJyYXJ5KGxtZXJUZXN0KQ0KbGlicmFyeShjYXIpDQpsaWJyYXJ5KGdnZWZmZWN0cykNCmxpYnJhcnkocGFydHkpDQpsaWJyYXJ5KHBhcnR5a2l0KQ0KbGlicmFyeShzdHJ1Y2NoYW5nZSkNCmxpYnJhcnkoZ2dwYXJ0eSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KG1vcmVwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShwUk9DKQ0KYGBgDQoNCiMgRGVmaW5lIHByaW50YWxsIGZ1bmN0aW9uDQoNCmBgYHtyfQ0KcHJpbnRhbGwgPC0gZnVuY3Rpb24odGliYmxlKSB7DQogIHByaW50KHRpYmJsZSwgd2lkdGggPSBJbmYpDQogIH0NCmBgYA0KDQojIExvYWQgZ2VvbV9mbGF0X3Zpb2xpbiBwbG90DQoNCmBgYHtyfQ0Kc291cmNlKCJodHRwczovL2dpc3QuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Jlbm1hcndpY2svMmExYmIwMTMzZmY1NjhjYmUyOGQvcmF3L2ZiNTNiZDk3MTIxZjdmOWNlOTQ3ODM3ZWYxYTRjNjVhNzNiZmZiM2YvZ2VvbV9mbGF0X3Zpb2xpbi5SIikNCmBgYA0KDQojIFJlYWQgUmVTdXJ2ZXkgZGF0YSB3aXRoIFJTIGluZGljYXRvcnMNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlM8LXJlYWRfdHN2KA0KICBoZXJlKCJkYXRhIiwgImNsZWFuIiwgImRiX3Jlc3Vydl9SU18yMDI1MDUyMy5jc3YiKSwNCiAgY29sX3R5cGVzID0gY29scygNCiAgICAjIER5bmFtaWNhbGx5IHNwZWNpZnkgRVVOSVMgY29sdW1ucyBhcyBjaGFyYWN0ZXINCiAgICAuZGVmYXVsdCA9IGNvbF9ndWVzcygpLCAgIyBEZWZhdWx0IGd1ZXNzaW5nIGZvciBvdGhlciBjb2x1bW5zDQogICAgRVVOSVNhID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYiA9IGNvbF9jaGFyYWN0ZXIoKSwNCiAgICBFVU5JU2MgPSBjb2xfY2hhcmFjdGVyKCksDQogICAgRVVOSVNkID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY180ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8xID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8yID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8zID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF80ID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8xX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTX2Fzc2lnbmF0aW9uID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYV80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTYl80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY18zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTY180X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8yX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF8zX2Rlc2NyID0gY29sX2NoYXJhY3RlcigpLA0KICAgIEVVTklTZF80X2Rlc2NyID0gY29sX2NoYXJhY3RlcigpDQogICAgKQ0KICApDQpgYGANCg0KTm8gcGFyc2luZyBpc3N1ZXMhDQoNCiMgU29tZSBkYXRhIG1hbmFnZW5lbXQNCg0KIyMgU2V2ZXJhbCBFVU5JUyBsZXZlbCAxIGFzc2lnbmVkDQoNCk51bWJlciBvZiByb3dzIHdoZXJlIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgRVVOSVMgMSBhc3NpZ25lZCwgYW5kIHRoZXkgYXJlIGRpZmZlcmVudCBhbW9uZyB0aGVtLiBTZWUgd2hhdCB0byBkbyB3aXRoIHRoZXNlIGxhdGVyISBTbyBmYXIgSSB0YWtlIEVVTklTYV8xLg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTICU+JSANCiAgICAgICAjIFJvd3Mgd2l0aCBtb3JlIHRoYW4gb25lIEVVTklTIDEgYXNzaWduZWQNCiAgICAgICBmaWx0ZXIoIWlzLm5hKEVVTklTYl8xKSkgJT4lIA0KICAgICAgIGZpbHRlcihFVU5JU2FfMSE9RVVOSVNiXzEgfCBFVU5JU2JfMSAhPSBFVU5JU2NfMSB8IEVVTklTYV8xICE9IEVVTklTY18xKSkNCmBgYA0KDQpTZWUgImNvbmZ1c2lvbnMiOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SUyAlPiUgDQogICMgUm93cyB3aXRoIG1vcmUgdGhhbiBvbmUgRVVOSVMgMSBhc3NpZ25lZA0KICBmaWx0ZXIoIWlzLm5hKEVVTklTYl8xKSkgJT4lIA0KICBmaWx0ZXIoRVVOSVNhXzEhPUVVTklTYl8xIHwgRVVOSVNiXzEgIT0gRVVOSVNjXzEgfCBFVU5JU2FfMSAhPSBFVU5JU2NfMSkgJT4lDQogIGRpc3RpbmN0KEVVTklTYV8xLCBFVU5JU2JfMSwgRVVOSVNjXzEsIEVVTklTZF8xKQ0KYGBgDQoNCkRlZmluZSAiY29uZnVzaW9uIiBjb2x1bW5zOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SUyA8LSBkYl9yZXN1cnZfUlMgJT4lDQogIG11dGF0ZShFVU5JUzFfY29uZl90eXBlID0gY2FzZV93aGVuKA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJTIiAmIEVVTklTYl8xID09ICJUIiB+ICJTL1QiLA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJSIiAmIEVVTklTY18xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJSIiAmIEVVTklTYl8xID09ICJSIiAmIEVVTklTY18xID09ICJTIiAmIEVVTklTZF8xID09ICJTIiB+ICJSL1MiLA0KICAgIEVVTklTYV8xID09ICJQIiAmIEVVTklTYl8xID09ICJRIiB+ICJQL1EiLA0KICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICBFVU5JUzFfY29uZiA9ICFpcy5uYShFVU5JUzFfY29uZl90eXBlKSkNCmBgYA0KDQojIyBUaWJibGUgd2l0aCBzZWxlY3RlZCBjb2x1bW5zDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0IDwtIGRiX3Jlc3Vydl9SUyAlPiUNCiAgc2VsZWN0KFBsb3RPYnNlcnZhdGlvbklELCBDb3VudHJ5LCBSU19DT0RFLCBgUmVTdXJ2ZXkgc2l0ZWAsIGBSZVN1cnZleSBwbG90YCwNCiAgICAgICAgIGBNYW5pcHVsYXRlICh5L24pYCwgYFR5cGUgb2YgbWFuaXB1bGF0aW9uYCwgTG9uX3VwZGF0ZWQsIExhdF91cGRhdGVkLA0KICAgICAgICAgYExvY2F0aW9uIG1ldGhvZGAsIGBMb2NhdGlvbiB1bmNlcnRhaW50eSAobSlgLCBFVU5JU2FfMSwNCiAgICAgICAgIEVVTklTYV8xX2Rlc2NyLCBFVU5JU2FfMiwgRVVOSVNhXzJfZGVzY3IsIEVVTklTYV8zLCBFVU5JU2FfM19kZXNjciwNCiAgICAgICAgIEVVTklTYV80LCBFVU5JU2FfNF9kZXNjciwgRVVOSVMxX2NvbmYsIEVVTklTMV9jb25mX3R5cGUsDQogICAgICAgICBkYXRlLCB5ZWFyLCBiaW9nZW8sIHVuaXQsIHllYXJfUlMsIExvbl9SUywgTGF0X1JTLCANCiAgICAgICAgIHN0YXJ0c193aXRoKCJORFZJIiksIHN0YXJ0c193aXRoKCJORE1JIiksIHN0YXJ0c193aXRoKCJORFdJIiksDQogICAgICAgICBzdGFydHNfd2l0aCgiRVZJIiksIHN0YXJ0c193aXRoKCJTQVZJIiksIGNhbm9weV9oZWlnaHQsDQogICAgICAgICBTT1NfRE9ZLCBTT1NfZGF0ZSwgTkRWSV9hdF9TT1MsIFBlYWtfRE9ZLCBQZWFrX2RhdGUsIE5EVklfYXRfUGVhaywNCiAgICAgICAgIEVPU19ET1ksIEVPU19kYXRlLCBORFZJX2F0X0VPUywgU2Vhc29uX0xlbmd0aCwNCiAgICAgICAgIFMyX2RhdGEsIExhbmRzYXRfZGF0YSwgQ0hfZGF0YSwgUzJfcGhlbl9kYXRhKQ0KYGBgDQoNCiMjIFRPLURPOiBNaXNzaW5nIGRhdGEgY2hlY2tzDQoNCkRvIHdoZW4gYWxsIFJTIGRhdGEgaXMgcmVhZHkhDQoNCiMjIEZsYWcgd2hlbiB5ZWFyIGlzIGRpZmZlcmVudCBiZXR3ZWVuIFJTIGRhdGEgYW5kIFJlU3VydmV5IGRiDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0IDwtIGRiX3Jlc3Vydl9SU19zaG9ydCAlPiUNCiAgbXV0YXRlKHllYXJfZGlmZiA9IHllYXIgIT0geWVhcl9SUykNCmBgYA0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydCAlPiUgY291bnQoeWVhcl9kaWZmKQ0KYGBgDQoNCk5vbmUgd2l0aCBkaWZmZXJlbnQgeWVhciBzbyBmYXIuDQoNCiMjIEZsYWcgd2hlbiBjb29yZGluYXRlcyBhcmUgZGlmZmVyZW50IGJldHdlZW4gUlMgZGF0YSBhbmQgUmVTdXJ2ZXkgZGINCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JQ0KICBtdXRhdGUoTG9uX2RpZmYgPSBjYXNlX3doZW4oTG9uX3VwZGF0ZWQgPT0gTG9uX1JTIH4gIk5PIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU29tZXRpbWVzIHRoZXkgYXJlIG9ubHkgc2xpZ2hseSBkaWZmZXJlbnQNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFicyhMb25fdXBkYXRlZCAtIExvbl9SUykgPCAwLjAxIH4gIlNNQUxMIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKExvbl91cGRhdGVkKSB8IGlzLm5hKExvbl9SUykgfiBOQSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiTEFSR0UiKSwNCiAgICAgICAgIExhdF9kaWZmID0gY2FzZV93aGVuKExhdF91cGRhdGVkID09IExhdF9SUyB+ICJOTyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNvbWV0aW1lcyB0aGV5IGFyZSBvbmx5IHNsaWdobHkgZGlmZmVyZW50DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhYnMoTGF0X3VwZGF0ZWQgLSBMYXRfUlMpIDwgMC4wMSB+ICJTTUFMTCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShMYXRfdXBkYXRlZCkgfCBpcy5uYShMYXRfUlMpIH4gTkEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIkxBUkdFIikpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgJT4lIGNvdW50KExvbl9kaWZmKQ0KZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JSBjb3VudChMYXRfZGlmZikNCmBgYA0KDQpWZXJ5IGZldyB3aXRoIGxhcmdlIGRpZmZlcmVuY2VzICgzIGZvciBsb25naXR1ZGUsIDUgZm9yIGxhdGl0dWRlKS4gUlMgaW5kaWNlcyB3b3VsZCBuZWVkIHRvIGJlIGNhbGN1bGF0ZWQgYWdhaW4gZm9yIHRob3NlLg0KDQpJZiB5ZWFyX2RpZmYgaXMgVFJVRSBvciBMb25fZGlmZiBpcyBMQVJHRSBvciBMYXRfZGlmZiBpcyBMQVJHRSAtLT4gUlMgaW5kaWNlcyBuZWVkIHRvIGJlIHJlY2FsY3VsYXRlZC4gU28gZmFyLCByZW1vdmUgdGhvc2Ugcm93cyBmcm9tIGRiX3Jlc3Vydl9SU19zaG9ydC4NCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0ICU+JQ0KICBmaWx0ZXIoeWVhcl9kaWZmID09IEZBTFNFIHwgaXMubmEoeWVhcl9kaWZmKSkgJT4lDQogIGZpbHRlcihMb25fZGlmZiA9PSAiTk8iIHxMb25fZGlmZiA9PSAiU01BTEwiIHwgaXMubmEoTG9uX2RpZmYpKSAlPiUNCiAgZmlsdGVyKExhdF9kaWZmID09ICJOTyIgfExhdF9kaWZmID09ICJTTUFMTCIgfCBpcy5uYShMYXRfZGlmZikpDQpgYGANCg0KIyMgVE8tRE86IFJlY2FsY3VsYXRlIFJTIGluZGljZXMgZm9yIHRob3NlIGFib3ZlDQoNCiMjIEhhbmRsZSBwbG90cyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSBvYnMgcGVyIHllYXINCg0KQWRkIGNvbHVtbiBQTE9UIHRvIGRhdGEgdG8gaWRlbnRpZnkgdW5pcXVlIHBsb3RzOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UIDwtIGRiX3Jlc3Vydl9SU19zaG9ydCAlPiUNCiAgIyBPcmlnaW5hbCBuYW1lcyBnaXZlIHByb2JsZW1zLCBjcmVhdGUgbmV3IHZhcnMNCiAgbXV0YXRlKFJTX3NpdGUgPSBgUmVTdXJ2ZXkgc2l0ZWAsIFJTX3Bsb3QgPSBgUmVTdXJ2ZXkgcGxvdGApICU+JQ0KICAjIENvbnZlcnQgdG8gZGF0YS50YWJsZSBmb3IgZmFzdGVyIHByb2Nlc3NpbmcNCiAgbGF6eV9kdCgpICU+JQ0KICAjIEdyb3VwIGJ5IHRoZSAzIHZhcnMgdGhhdCB1bmlxdWVseSBpZGVudGlmeSBlYWNoIHBsb3QNCiAgZ3JvdXBfYnkoUlNfQ09ERSwgUlNfc2l0ZSwgUlNfcGxvdCkgJT4lDQogICMgQ3JlYXRlIGEgbmV3IHZhcmlhYmxlIFBMT1QgZm9yIGVhY2ggZ3JvdXANCiAgbXV0YXRlKFBMT1QgPSAuR1JQKSAlPiUNCiAgIyBDb252ZXJ0IGJhY2sgdG8gdGliYmxlDQogIGFzX3RpYmJsZSgpICU+JQ0KICAjIFJlbW92ZSB1bm5lZWRlZCB2YXJzDQogIHNlbGVjdCgtUlNfc2l0ZSwgLVJTX3Bsb3QpDQpgYGANCg0KVGhlcmUgc2hvdWxkIGJlIG9ubHkgb25lIG9ic2VydmF0aW9uIG9mIGVhY2ggcGxvdCBwZXIgeWVhci4NCg0KUGxvdHMgd2hlcmUgdGhlcmUgaXMgYXQgbGVhc3QgYSB5ZWFyIHdpdGggbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiwgYW5kIHdoZXJlIHRob3NlIG9ic2VydmF0aW9ucyBoYXZlIGEgZGlmZmVyZW50IEVVTklTIGFzc2lnbmVkOg0KDQpgYGB7cn0NCnBsb3RzX3RvX3JlbW92ZSA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZ3JvdXBfYnkoUExPVCwgeWVhcikgJT4lDQogIHN1bW1hcml6ZShFVU5JU2FfMV9uID0gbl9kaXN0aW5jdChFVU5JU2FfMSwgbmEucm0gPSBUUlVFKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUgDQogIGZpbHRlcihFVU5JU2FfMV9uID4gMSkgJT4lDQogIGRpc3RpbmN0KFBMT1QpDQpgYGANCg0KUmVtb3ZlIHBsb3RzX3RvX3JlbW92ZSBmcm9tIHRoZSBkYXRhYmFzZToNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgYW50aV9qb2luKHBsb3RzX3RvX3JlbW92ZSwgYnkgPSAiUExPVCIpDQpgYGANCg0KUGxvdHMgYW5kIHllYXJzIHdoZXJlIHRoZXJlIGlzIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb246DQoNCmBgYHtyfQ0KcGxvdHNfdG9fbWVyZ2UgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGdyb3VwX2J5KFBMT1QsIHllYXIpICU+JQ0KICAjIFBsb3RzIHRoYXQgaGF2ZSBtb3JlIHRoYW4gb25lIG9ic2VydmF0aW9uIHBlciB5ZWFyDQogIGZpbHRlcihuKCkgPiAxKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBkaXN0aW5jdChQTE9UKQ0KYGBgDQoNClN1bW1hcml6ZSBwbG90c190b19tZXJnZToNCg0KYGBge3J9DQpwbG90c190b19tZXJnZV9zdW1tIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBncm91cF9ieShQTE9ULCB5ZWFyKSAlPiUNCiAgIyBQbG90cyB0aGF0IGhhdmUgbW9yZSB0aGFuIG9uZSBvYnNlcnZhdGlvbiBwZXIgeWVhcg0KICBmaWx0ZXIobigpID4gMSkgJT4lDQogIG11dGF0ZShvYnNfbnVtID0gcm93X251bWJlcigpKSAlPiUNCiAgcGl2b3Rfd2lkZXIoDQogICAgbmFtZXNfZnJvbSA9IG9ic19udW0sDQogICAgdmFsdWVzX2Zyb20gPSBjKGRhdGUsIFBsb3RPYnNlcnZhdGlvbklEKSwNCiAgICBuYW1lc19wcmVmaXggPSAib2JzXyINCiAgKSAlPiUNCiAgYXJyYW5nZShQTE9UKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIGFjcm9zcyhjKENvdW50cnksIFJTX0NPREUsIGBSZVN1cnZleSBzaXRlYCwgYFJlU3VydmV5IHBsb3RgLA0KICAgICAgICAgICAgIGBNYW5pcHVsYXRlICh5L24pYCwgYFR5cGUgb2YgbWFuaXB1bGF0aW9uYCwgTG9uX3VwZGF0ZWQsDQogICAgICAgICAgICAgTGF0X3VwZGF0ZWQsIGBMb2NhdGlvbiBtZXRob2RgLCBgTG9jYXRpb24gdW5jZXJ0YWludHkgKG0pYCwNCiAgICAgICAgICAgICBFVU5JU2FfMSwgRVVOSVNhXzFfZGVzY3IsIEVVTklTYV8yLCBFVU5JU2FfMl9kZXNjciwgRVVOSVNhXzMsDQogICAgICAgICAgICAgRVVOSVNhXzNfZGVzY3IsIEVVTklTYV80LCBFVU5JU2FfNF9kZXNjciwgRVVOSVMxX2NvbmYsDQogICAgICAgICAgICAgRVVOSVMxX2NvbmZfdHlwZSwgYmlvZ2VvLCB1bml0LCB5ZWFyX1JTLCBMb25fUlMsIExhdF9SUywgTkRWSV9tYXgsDQogICAgICAgICAgICAgTkRWSV9tZWRpYW4sIE5EVklfbWluLCBORFZJX21vZGUsIE5EVklfcDEwLCBORFZJX3A5MCwgTkRNSV9tYXgsDQogICAgICAgICAgICAgTkRNSV9tZWRpYW4sIE5ETUlfbWluLCBORE1JX21vZGUsIE5ETUlfcDEwLCBORE1JX3A5MCwgTkRXSV9tYXgsDQogICAgICAgICAgICAgTkRXSV9tZWRpYW4sIE5EV0lfbWluLCBORFdJX21vZGUsIE5EV0lfcDEwLCBORFdJX3A5MCwgRVZJX21heCwNCiAgICAgICAgICAgICBFVklfbWVkaWFuLCBFVklfbWluLCBFVklfbW9kZSwgRVZJX3AxMCwgRVZJX3A5MCwgU0FWSV9tYXgsDQogICAgICAgICAgICAgU0FWSV9tZWRpYW4sIFNBVklfbWluLCBTQVZJX21vZGUsIFNBVklfcDEwLCBTQVZJX3A5MCwNCiAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0LCBTT1NfRE9ZLCBTT1NfZGF0ZSwgUGVha19ET1ksIFBlYWtfZGF0ZSwgRU9TX0RPWSwNCiAgICAgICAgICAgICBFT1NfZGF0ZSwgUzJfZGF0YSwgTGFuZHNhdF9kYXRhLCBDSF9kYXRhLCBTMl9waGVuX2RhdGEsIHllYXJfZGlmZiwNCiAgICAgICAgICAgICBMb25fZGlmZiwgTGF0X2RpZmYpLCBmaXJzdCksDQogICAgYWNyb3NzKHN0YXJ0c193aXRoKCJkYXRlX29ic18iKSwgbWluKSwNCiAgICBhY3Jvc3Moc3RhcnRzX3dpdGgoIlBsb3RPYnNlcnZhdGlvbklEX29ic18iKSwgbWluKQ0KICAgICkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNClJlbW92ZSBwbG90c190b19tZXJnZSBmcm9tIHRoZSBkYXRhYmFzZToNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgYW50aV9qb2luKHBsb3RzX3RvX21lcmdlLCBieSA9ICJQTE9UIikNCmBgYA0KDQpBbmQgYWRkIHBsb3RzX3RvX21lcmdlX3N1bW0sIHdoZXJlIGVhY2ggcGxvdCBhbmQgeWVhciBvbmx5IGhhcyBvbmUgcm93Og0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UIDwtIGJpbmRfcm93cyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90c190b19tZXJnZV9zdW1tKQ0KYGBgDQoNCkNoZWNrIHRoYXQgdGhlcmUgaXMgb25seSBvbmUgcm93IHBlciBwbG90IGFuZCBwZXIgeWVhcjoNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZ3JvdXBfYnkoUExPVCwgeWVhcikgJT4lDQogICMgUGxvdHMgdGhhdCBoYXZlIG1vcmUgdGhhbiBvbmUgb2JzZXJ2YXRpb24gcGVyIHllYXINCiAgZmlsdGVyKG4oKSA+IDEpIA0KYGBgDQoNClNvLCB0byBzdW0gdXAgd2hhdCBJIGhhdmUgZG9uZToNCg0KLSBQbG90cyB3aGVyZSB0aGVyZSBpcyBhdCBsZWFzdCBhIHllYXIgd2l0aCBtb3JlIHRoYW4gb25lIG9ic2VydmF0aW9uLCBhbmQgd2hlcmUgdGhvc2Ugb2JzZXJ2YXRpb25zIGhhdmUgYSBkaWZmZXJlbnQgRVVOSVMgYXNzaWduZWQ6IFBsb3RzIFJFTU9WRUQgZnJvbSB0aGUgZGF0YQ0KLSBQbG90cyB3aGVyZSB0aGVyZSBpcyBtb3JlIHRoYW4gb25lIG9ic2VydmF0aW9uLCBidXQgb2JzZXJ2YXRpb25zIGhhdmUgdGhlIHNhbWUgRVVOSVMgYXNzaWduZWQ6IGtlcHQgaW4gdGhlIGRhdGEuIE1lcmdlZCBzbyB0aGF0IHRoZXJlIGlzIG9ubHkgb25lIHJvdyBwZXIgeWVhci4gSW5mbyBhYm91dCB0aGUgZGlmZmVyZW50IGRhdGVzICh3aGVuIGRpZmZlcmVudCkgaXMga2VwdCBpbiBjb2x1bW5zIGRhdGVfb2JzXzEgLSBkYXRlX29ic180MCwgYW5kIGluZm8gYWJvdXQgdGhlIGRpZmZlcmVudCBQbG90T2JzZXJ2YXRpb25JRCBpcyBrZXB0IGluIHRoZSBjb2x1bW5zIFBsb3RPYnNlcnZhdGlvbklEX29ic18xIC0gUGxvdE9ic2VydmF0aW9uSURfb2JzXzQwLg0KDQojIyMgU2F2ZSB0byBjbGVhbiBkYXRhDQoNClNhdmUgY2xlYW4gZmlsZSBmb3IgYW5hbHlzZXMgKHRvIGJlIHVwZGF0ZWQgY29udGludW91c2x5IGR1ZSB0byB1cGRhdGVzIGluIFJlU3VydmV5IGRhdGFiYXNlIGFuZCB1cGRhdGVzIG9uIFJTIGRhdGEpLg0KDQpgYGB7cn0NCndyaXRlX3RzdihkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICBoZXJlKCJkYXRhIiwgImNsZWFuIiwiZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfMjAyNTA1MjMuY3N2IikpDQpgYGANCg0KIyBEaXN0cmlidXRpb25zIGFsbCBiaW9yZWdpb25zDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgaGlzdG9ncmFtcw0KcGxvdF9oaXN0b2dyYW0gPC0gZnVuY3Rpb24oZGF0YSwgeF92YXIsIHhfbGFiZWwpIHsNCiAgZ2dwbG90KGRhdGEgJT4lDQogICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgICBhZXMoeCA9ICEhc3ltKHhfdmFyKSkpICsNCiAgICBnZW9tX2hpc3RvZ3JhbShjb2xvciA9ICJibGFjayIsIGZpbGwgPSAid2hpdGUiKSArDQogICAgbGFicyh4ID0geF9sYWJlbCwgeSA9ICJGcmVxdWVuY3kiKSArDQogICAgdGhlbWVfYncoKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KIyBEZWZpbmUgYSBmdW5jdGlvbiB0byBjcmVhdGUgcGxvdHMgd2l0aCB2aW9saW4gKyBib3hwbG90ICsgcG9pbnRzDQpkaXN0cl9wbG90IDwtIGZ1bmN0aW9uKGRhdGEsIHlfdmFycywgeV9sYWJlbHMpIHsNCiAgZm9yIChpIGluIHNlcV9hbG9uZyh5X3ZhcnMpKSB7DQogICAgeV92YXIgPC0geV92YXJzW1tpXV0NCiAgICB5X2xhYmVsIDwtIHlfbGFiZWxzW1tpXV0NCiAgICANCiAgICBwIDwtIGdncGxvdChkYXRhID0gZGF0YSAlPiUNCiAgICAgICAgICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSksDQogICAgICAgICAgICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9ICEhc3ltKHlfdmFyKSwgZmlsbCA9IEVVTklTYV8xX2Rlc2NyKSkgKw0KICAgICAgZ2VvbV9mbGF0X3Zpb2xpbihwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjIsIHkgPSAwKSwgYWxwaGEgPSAwLjgpICsNCiAgICAgIGdlb21fcG9pbnQoYWVzKHkgPSAhIXN5bSh5X3ZhciksIGNvbG9yID0gRVVOSVNhXzFfZGVzY3IpLA0KICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcih3aWR0aCA9IDAuMTUpLCBzaXplID0gMSwgYWxwaGEgPSAwLjI1KSArDQogICAgICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgICAgIHN0YXRfc3VtbWFyeShmdW4ueSA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDIwLCBzaXplID0gMSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZnVuY3Rpb24oeCkgZGF0YS5mcmFtZSh5ID0gbWF4KHgpICsgMC4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IGxlbmd0aCh4KSksDQogICAgICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgICAgIGxhYnMoeSA9IHlfbGFiZWwsIHggPSAiRVVOSVMgbGV2ZWwgMSIpICsNCiAgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgICAgIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkNCiAgICANCiAgICBwcmludChwKQ0KICB9DQp9DQpgYGANCg0KIyMgTkRWSSwgTkRNSSwgTkRXSSwgU0FWSSBhbmQgRVZJDQoNClJhbmdlcyBvZiBtaW4gYW5kIG1heDoNCg0KYGBge3J9DQpyYW5nZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCRORFZJX21heCwgbmEucm0gPSBUKQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkTkRNSV9tYXgsIG5hLnJtID0gVCkNCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJE5EV0lfbWF4LCBuYS5ybSA9IFQpDQpyYW5nZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCRTQVZJX21heCwgbmEucm0gPSBUKQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkRVZJX21heCwgbmEucm0gPSBUKQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkTkRWSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJE5ETUlfbWluLCBuYS5ybSA9IFQpDQpyYW5nZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCRORFdJX21pbiwgbmEucm0gPSBUKQ0KcmFuZ2UoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QkU0FWSV9taW4sIG5hLnJtID0gVCkNCnJhbmdlKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UJEVWSV9taW4sIG5hLnJtID0gVCkNCmBgYA0KDQpIaXN0b2dyYW1zIHRvIGNoZWNrIHRoYXQgbWF4IGFuZCBtaW4gdmFsdWVzIGFyZSBvazoNCg0KYGBge3J9DQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIk5EVklfbWF4IiwgIk5EVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRNSV9tYXgiLCAiTkRNSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJORFdJX21heCIsICJORFdJIG1heCIpDQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIlNBVklfbWF4IiwgIlNBVkkgbWF4IikNCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSANCiAgICAgICAgICAgICAgICAgIyBTb21lIHZhbHVlcyB3cm9uZyENCiAgICAgICAgICAgICAgICAgZmlsdGVyKEVWSV9tYXggPD0gMSksICJFVklfbWF4IiwgIkVWSSBtYXgiKQ0KcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJORFZJX21pbiIsICJORFZJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwgIk5ETUlfbWluIiwgIk5ETUkgbWluIikNCnBsb3RfaGlzdG9ncmFtKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiTkRXSV9taW4iLCAiTkRXSSBtaW4iKQ0KcGxvdF9oaXN0b2dyYW0oZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsICJTQVZJX21pbiIsICJTQVZJIG1pbiIpDQpwbG90X2hpc3RvZ3JhbShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgDQogICAgICAgICAgICAgICAgICMgU29tZSB2YWx1ZXMgd3JvbmchDQogICAgICAgICAgICAgICAgIGZpbHRlcihFVklfbWluID49IC0xICYgRVZJX21pbiA8PSAxKSwgIkVWSV9taW4iLCAiRVZJIG1pbiIpDQpgYGANCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgZmlsdGVyKEVWSV9tYXggPiAxKSkNCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSklPiUNCiAgZmlsdGVyKEVWSV9tYXggPiAxKSAlPiUNCiAgY291bnQoYmlvZ2VvLCB1bml0KQ0KYGBgDQoNClNvIGZhciwgcmVtb3ZlIG9ic2VydmF0aW9ucyB3aXRoIEVWSV9tYXggPiAxIGFuZCBFVklfbWluIDwtMSBvciA+IDEgZnJvbSBzdHVmZiB1c2luZyBFVkkgdmFsdWVzLiANCg0KRGlzdHJpYnV0aW9uIHBsb3RzOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULA0KICAgICAgICAgICBjKCJORE1JX21heCIsICJORE1JX3A5MCIsICJORE1JX21pbiIsICJORE1JX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRNSSBtYXgiLCAiTkRNSSBwOTAiLCAiTkRNSSBtaW4iLCAiTkRNSSBwMTAiKSkNCmRpc3RyX3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QsDQogICAgICAgICAgIGMoIk5EV0lfbWF4IiwgIk5EV0lfcDkwIiwgIk5EV0lfbWluIiwgIk5EV0lfcDEwIiksIA0KICAgICAgICAgICBjKCJORFdJIG1heCIsICJORFdJIHA5MCIsICJORFdJIG1pbiIsICJORFdJIHAxMCIpKQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgYygiU0FWSV9tYXgiLCAiU0FWSV9wOTAiLCAiU0FWSV9taW4iLCAiU0FWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIlNBVkkgbWF4IiwgIlNBVkkgcDkwIiwgIlNBVkkgbWluIiwgIlNBVkkgcDEwIikpDQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgICAgIGZpbHRlcihFVklfbWF4IDw9IDEpICU+JQ0KICAgICAgICAgICAgIGZpbHRlcihFVklfbWluID49IC0xICYgRVZJX21pbiA8PSAxKSwNCiAgICAgICAgICAgYygiRVZJX21heCIsICJFVklfcDkwIiwgIkVWSV9taW4iLCAiRVZJX3AxMCIpLCANCiAgICAgICAgICAgYygiRVZJIG1heCIsICJFVkkgcDkwIiwgIkVWSSBtaW4iLCAiRVZJIHAxMCIpKQ0KYGBgDQoNClNvbWUgd2VpcmQgdmFsdWVzIC0tPiBDaGVjay4NCg0KIyMgQ0gNCg0KYGBge3J9DQpkaXN0cl9wbG90KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiY2Fub3B5X2hlaWdodCIsICJDYW5vcHkgaGVpZ2h0IChtKSIpDQpgYGANCiANCiMjIyBTaG93IGhhYml0YXRzIHdpdGggQ0ggY2F0ZWdvcmllcw0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICMgS2VlcCBvbmx5IGZvcmVzdHMsIGdyYXNzbGFuZHMsIHNocnVibGFuZHMgYW5kIHdldGxhbmRzDQogICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgICAgbXV0YXRlKENIX2NhdCA9DQogICAgICAgICAgICAgICAgICBmYWN0b3IoDQogICAgICAgICAgICAgICAgICAgIGNhc2Vfd2hlbihjYW5vcHlfaGVpZ2h0ID09IDAgfiAiMCBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiAwICYgY2Fub3B5X2hlaWdodCA8PSAxIH4gIjAtMSBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiAxICYgY2Fub3B5X2hlaWdodCA8PTIgfiAiMS0yIG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2Fub3B5X2hlaWdodCA+IDIgJiBjYW5vcHlfaGVpZ2h0IDw9NSB+ICIyLTUgbSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYW5vcHlfaGVpZ2h0ID4gNSAmIGNhbm9weV9oZWlnaHQgPD04IH4gIjUtOCBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbm9weV9oZWlnaHQgPiA4IH4gIj4gOCBtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKGNhbm9weV9oZWlnaHQpIH4gTkFfY2hhcmFjdGVyXyksDQogICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoDQogICAgICAgICAgICAgICAgICAgICAgIjAgbSIsICIwLTEgbSIsICIxLTIgbSIsICIyLTUgbSIsICI1LTggbSIsICI+IDggbSIpKSksDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgZmlsbCA9IENIX2NhdCkpICsNCiAgZ2VvbV9iYXIoKSArIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfbnVtYmVyKCkpICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2QoZGlyZWN0aW9uID0gLTEpICsNCiAgbGFicyh4ID0gIkVVTklTIGxldmVsIDEiLCBmaWxsID0gIkNhbm9weSBoZWlnaHQiKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjgsIDAuNzUpLA0KICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIikNCmBgYA0KDQojIyMgU3RhdHMgcGVyIGhhYml0YXQgdHlwZQ0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBncm91cF9ieShFVU5JU2FfMV9kZXNjcikgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoY2Fub3B5X2hlaWdodCwgbGlzdCgNCiAgICBtZWFuID0gbWVhbiwNCiAgICBtZWRpYW4gPSBtZWRpYW4sDQogICAgc2QgPSBzZCwNCiAgICBtaW4gPSBtaW4sDQogICAgbWF4ID0gbWF4DQogICAgKSwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQojIyBQaGVub2xvZ3kNCg0KIyMjIENhbGN1bGF0ZSBtZXRyaWNzDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIG11dGF0ZSgNCiAgICAjIERpZmZlcmVuY2UgTkRWSSBiZXR3ZWVuIFBlYWsgYW5kIFNPUw0KICAgIGRpZmZfUGVha19TT1MgPSBORFZJX2F0X1BlYWsgLSBORFZJX2F0X1NPUywNCiAgICAjIERpZmZlcmVuY2UgTkRWSSBiZXR3ZWVuIFBlYWsgYW5kIEVPUw0KICAgIGRpZmZfUGVha19FT1MgPSBORFZJX2F0X1BlYWsgLSBORFZJX2F0X0VPUykNCmBgYA0KDQojIyMgSGlzdG9ncmFtcyBwaGVub2xvZ3kgbWVhc3VyZXMNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgIyBLZWVwIG9ubHkgZm9yZXN0cywgZ3Jhc3NsYW5kcywgc2hydWJsYW5kcyBhbmQgd2V0bGFuZHMNCiAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSAmIFMyX3BoZW5fZGF0YSA9PSBUKSAlPiUNCiAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gYyhTT1NfRE9ZLCBQZWFrX0RPWSwgRU9TX0RPWSksIG5hbWVzX3RvID0gIm5hbWUiLA0KICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZSIpLA0KICAgICAgIGFlcyh4ID0gdmFsdWUpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsNCiAgZmFjZXRfZ3JpZChiaW9nZW8gfiBuYW1lLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICB0aGVtZV9idygpDQpnZ3Bsb3QoZGF0YSA9IGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgIyBLZWVwIG9ubHkgZm9yZXN0cywgZ3Jhc3NsYW5kcywgc2hydWJsYW5kcyBhbmQgd2V0bGFuZHMNCiAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSAmIFMyX3BoZW5fZGF0YSA9PSBUKSAlPiUNCiAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gYyhORFZJX2F0X1NPUywgTkRWSV9hdF9QZWFrLCBORFZJX2F0X0VPUyksDQogICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibmFtZSIsIHZhbHVlc190byA9ICJ2YWx1ZSIpLA0KICAgICAgIGFlcyh4ID0gdmFsdWUpKSArDQogIGdlb21faGlzdG9ncmFtKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsNCiAgZmFjZXRfZ3JpZChiaW9nZW8gfiBuYW1lLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICB0aGVtZV9idygpDQpnZ3Bsb3QoZGF0YSA9IGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgICAgIyBLZWVwIG9ubHkgZm9yZXN0cywgZ3Jhc3NsYW5kcywgc2hydWJsYW5kcyBhbmQgd2V0bGFuZHMNCiAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSAmIFMyX3BoZW5fZGF0YSA9PSBUKSAlPiUNCiAgICAgICAgIHBpdm90X2xvbmdlcihjb2xzID0gYyhkaWZmX1BlYWtfU09TLCBkaWZmX1BlYWtfRU9TKSwNCiAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJuYW1lIiwgdmFsdWVzX3RvID0gInZhbHVlIiksDQogICAgICAgYWVzKHggPSB2YWx1ZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBmYWNldF9ncmlkKGJpb2dlbyB+IG5hbWUsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIHRoZW1lX2J3KCkNCmdncGxvdChkYXRhID0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAjIEtlZXAgb25seSBmb3Jlc3RzLCBncmFzc2xhbmRzLCBzaHJ1YmxhbmRzIGFuZCB3ZXRsYW5kcw0KICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpICYgUzJfcGhlbl9kYXRhID09IFQpICU+JQ0KICAgICAgICAgcGl2b3RfbG9uZ2VyKGNvbHMgPSBjKFNlYXNvbl9MZW5ndGgpLA0KICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm5hbWUiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKSwNCiAgICAgICBhZXMoeCA9IHZhbHVlKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSArDQogIGZhY2V0X2dyaWQoYmlvZ2VvIH4gbmFtZSwgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMjIyBEaXN0cmlidXRpb25zDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCwNCiAgICAgICAgICAgYygiU09TX0RPWSIsIlBlYWtfRE9ZIiwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICJORFZJX2F0X1NPUyIsICJORFZJX2F0X1BlYWsiLCAiTkRWSV9hdF9FT1MiLA0KICAgICAgICAgICAgICJkaWZmX1BlYWtfU09TIiwiZGlmZl9QZWFrX0VPUyIsICJTZWFzb25fTGVuZ3RoIiksDQogICAgICAgICAgIGMoIlNPUyBET1kiLCAiUGVhayBET1kiLCAiRU9TIERPWSIsDQogICAgICAgICAgICAgIk5EVkkgYXQgU09TIiwgIk5EVkkgYXQgUGVhayIsICJORFZJIGF0IEVPUyIsDQogICAgICAgICAgICAgIkRpZmZlcmVuY2UgUGVhay1TT1MiLCAiRGlmZmVyZW5jZSBQZWFrLUVPUyIsICJTZWFzb24gTGVuZ3RoIikpDQpgYGANCg0KIyBEaXN0cmlidXRpb25zIHBlciBiaW9yZWdpb24NCg0KYGBge3J9DQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNyZWF0ZSBwbG90cyB3aXRoIHZpb2xpbiArIGJveHBsb3QgKyBwb2ludHMNCmRpc3RyX3Bsb3RfYmlvZ2VvIDwtIGZ1bmN0aW9uKGRhdGEsIHlfdmFycywgeV9sYWJlbHMpIHsNCiAgcGxvdHMgPC0gbGlzdCgpDQogIA0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHlfdmFycykpIHsNCiAgICB5X3ZhciA8LSB5X3ZhcnNbW2ldXQ0KICAgIHlfbGFiZWwgPC0geV9sYWJlbHNbW2ldXQ0KICAgIA0KICAgIHAgPC0gZ2dwbG90KGRhdGEgPSBkYXRhICU+JQ0KICAgICAgICAgICAgICAgICAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSwNCiAgICAgICAgICAgICAgICBhZXMoeCA9IEVVTklTYV8xX2Rlc2NyLCB5ID0gISFzeW0oeV92YXIpLCBmaWxsID0gRVVOSVNhXzFfZGVzY3IpKSArDQogICAgICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCkgKw0KICAgICAgZ2VvbV9wb2ludChhZXMoeSA9ICEhc3ltKHlfdmFyKSwgY29sb3IgPSBFVU5JU2FfMV9kZXNjciksDQogICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xNSksIHNpemUgPSAxLCBhbHBoYSA9IDAuMjUpICsNCiAgICAgIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMiwgb3V0bGllci5zaGFwZSA9IE5BLCBhbHBoYSA9IDAuNSkgKw0KICAgICAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogICAgICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSBmdW5jdGlvbih4KSBkYXRhLmZyYW1lKHkgPSBtYXgoeCkgKyAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gbGVuZ3RoKHgpKSwNCiAgICAgICAgICAgICAgICAgICBnZW9tID0gInRleHQiLCBhZXMobGFiZWwgPSAuLmxhYmVsLi4pLCB2anVzdCA9IDAuNSkgKw0KICAgICAgbGFicyh5ID0geV9sYWJlbCwgeCA9ICJFVU5JU2FfMV9kZXNjciIpICsNCiAgICAgIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgICAgIGd1aWRlcyhmaWxsID0gRkFMU0UsIGNvbG9yID0gRkFMU0UpICsNCiAgICAgIHRoZW1lX2J3KCkgKyBjb29yZF9mbGlwKCkgKyBmYWNldF93cmFwKH4gYmlvZ2VvKQ0KICAgIA0KICAgIHBsb3RzW1t5X3Zhcl1dIDwtIHANCiAgfQ0KICANCiAgcmV0dXJuKHBsb3RzKQ0KfQ0KYGBgDQoNCiMjIE5EVkksIE5ETUksIE5EV0ksIFNBVkkgYW5kIEVWSQ0KDQpEaXN0cmlidXRpb24gcGxvdHM6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgYygiTkRWSV9tYXgiLCAiTkRWSV9wOTAiLCAiTkRWSV9taW4iLCAiTkRWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EVkkgbWF4IiwgIk5EVkkgcDkwIiwgIk5EVkkgbWluIiwgIk5EVkkgcDEwIikpDQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgYygiTkRNSV9tYXgiLCAiTkRNSV9wOTAiLCAiTkRNSV9taW4iLCAiTkRNSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5ETUkgbWF4IiwgIk5ETUkgcDkwIiwgIk5ETUkgbWluIiwgIk5ETUkgcDEwIikpDQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgYygiTkRXSV9tYXgiLCAiTkRXSV9wOTAiLCAiTkRXSV9taW4iLCAiTkRXSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5EV0kgbWF4IiwgIk5EV0kgcDkwIiwgIk5EV0kgbWluIiwgIk5EV0kgcDEwIikpDQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgYygiU0FWSV9tYXgiLCAiU0FWSV9wOTAiLCAiU0FWSV9taW4iLCAiU0FWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIlNBVkkgbWF4IiwgIlNBVkkgcDkwIiwgIlNBVkkgbWluIiwgIlNBVkkgcDEwIikpDQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSAlPiUNCiAgICAgICAgICAgICBmaWx0ZXIoRVZJX21heCA8PSAxKSAlPiUNCiAgICAgICAgICAgICBmaWx0ZXIoRVZJX21pbiA+PSAtMSAmIEVWSV9taW4gPD0gMSksDQogICAgICAgICAgIGMoIkVWSV9tYXgiLCAiRVZJX3A5MCIsICJFVklfbWluIiwgIkVWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIkVWSSBtYXgiLCAiRVZJIHA5MCIsICJFVkkgbWluIiwgIkVWSSBwMTAiKSkNCmBgYA0KDQojIyBDSA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3RfYmlvZ2VvKGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9ULCAiY2Fub3B5X2hlaWdodCIsICJDYW5vcHkgaGVpZ2h0IChtKSIpDQpgYGANCg0KSW4gdGhpcyBwbG90LCB0aG9zZSB3aXRoIGJpb2dlbyA9IE5BIGFyZSB0aG9zZSB0aGF0IGRvIG5vdCBoYXZlIFMyIG9yIExhbmRzYXQgZGF0YSAoYW5kIHRodXMgYmlvZ2VvIGhhcyBub3QgYmVlbiBhc3NpZ25lZCksIGJ1dCBoYXZlIENIIGRhdGEuIFdlIHNob3VsZCBsYXRlciBhc3NpZ24gYSBiaW9nZW8gYmFzZWQgb24gbG9jYXRpb24uIA0KDQojIyBQaGVub2xvZ3kNCg0KYGBge3J9DQpkaXN0cl9wbG90X2Jpb2dlbyhkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKCFpcy5uYShiaW9nZW8pKSwNCiAgICAgICAgICAgICAgICAgIGMoIlNPU19ET1kiLCAiUGVha19ET1kiLCAiRU9TX0RPWSIsDQogICAgICAgICAgICAgICAgICAgICJORFZJX2F0X1NPUyIsICJORFZJX2F0X1BlYWsiLCAiTkRWSV9hdF9FT1MiLA0KICAgICAgICAgICAgICAgICAgICAiZGlmZl9QZWFrX1NPUyIsICJkaWZmX1BlYWtfRU9TIiwgIlNlYXNvbl9MZW5ndGgiKSwNCiAgICAgICAgICAgICAgICAgIGMoIlNPUyBET1kiLCAiUGVhayBET1kiLCAiRU9TIERPWSIsDQogICAgICAgICAgICAgICAgICAgICJORFZJIGF0IFNPUyIsICJORFZJIGF0IFBlYWsiLCAiTkRWSSBhdCBFT1MiLA0KICAgICAgICAgICAgICAgICAgICAiRGlmZmVyZW5jZSBQZWFrLVNPUyIsICJEaWZmZXJlbmNlIFBlYWstRU9TIiwNCiAgICAgICAgICAgICAgICAgICAgIlNlYXNvbiBMZW5ndGgiKSkNCmBgYA0KDQpCT1IgbWlzc2luZyBiZWNhdXNlIHRoZXJlIGlzIG5vIHBoZW5vbG9neSBpbmZvIGZvciBFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKS4NCg0KIyBIRVJFOiBWZXJpZnkgU09TLVBlYWtfRU9TIE9EWQ0KDQpFUlJPUlMhIEJlYSBpcyBjaGVja2luZyB0aGlzOg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoU09TX0RPWSA+IFBlYWtfRE9ZKQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihQZWFrX0RPWSA+IEVPU19ET1kpDQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKFNPU19ET1kgPiBFT1NfRE9ZKQ0KYGBgDQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lIGZpbHRlcihORFZJX2F0X1BlYWsgPCBORFZJX2F0X1NPUykNCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JSBmaWx0ZXIoTkRWSV9hdF9QZWFrIDwgTkRWSV9hdF9FT1MpDQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUgZmlsdGVyKE5EVklfYXRfUGVhayA8IE5EVklfbWF4KQ0KYGBgDQoNCiMgSW5jbHVkaW5nIFBMT1QgKFVTRSBMQVRFUj8pDQoNClN1bW1hcml6ZSB2YXJpYWJsZXMgYnkgcGxvdDoNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF9zdW1tIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQpICU+JQ0KICBncm91cF9ieShQTE9UKSAlPiUNCiAgc3VtbWFyaXplKEVVTklTMSA9IGlmX2Vsc2Uobl9kaXN0aW5jdChFVU5JU2FfMSkgPiAxLCAiQ2hhbmdlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pcXVlKEVVTklTYV8xKVsxXSksDQogICAgICAgICAgICBjb3VudCA9IG4oKSwNCiAgICAgICAgICAgIGFjcm9zcyhzdGFydHNfd2l0aCgiTkRWSSIpLCBsaXN0KG1lYW4gPSBtZWFuLCBzZCA9IHNkKSwNCiAgICAgICAgICAgICAgICAgICAubmFtZXMgPSAie2NvbH1fe2ZufSIpKQ0KDQpgYGANCg0KTWF5YmUgdXNlIGxhdGVyIGJlY2F1c2Ugbm93IG1hbnkgcGxvdHMgaGF2ZSBvbmx5IG9uZSBvYnNlcnZhdGlvbiwgcHJvYmFibHkgYmVjYXVzZSBzb21lIExhbmRzYXQgZGF0YSBpcyBtaXNzaW5nPw0KDQojIEZpcnN0IHZhbGlkYXRpb24NCg0KRm9yIFQsIFIsIFMsIFEgaGFiaXRhdHMuDQoNCkRlZmluZSBhIHNldCBvZiBydWxlcyBmb3IgYSBmaXJzdCB2YWxpZGF0aW9uIG9mIEFMTCBSZVN1cnZleSBkYXRhLiBXZSBjYW4gY2FsbCB0aGVzZSAiRXhwZXJ0LWJhc2VkIiBydWxlcy4NCg0KTnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBSZVN1cnZleSBmcm9tIHRoZSBoYWJpdGF0cyBvZiBpbnRlcmVzdDoNCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkpDQpgYGANCg0KTnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBSZVN1cnZleSBmcm9tIHRoZSBoYWJpdGF0cyBvZiBpbnRlcmVzdCBhbmQgd2l0aCBhbGwgUlMgZGF0YToNCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UICU+JQ0KICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgZmlsdGVyKENIX2RhdGEgPT0gVCkgJT4lDQogICAgICAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PVQpICU+JQ0KICAgICAgIGZpbHRlcihTMl9waGVuX2RhdGEgPT0gVCkpDQpgYGANCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKQ0KYGBgDQoNCiMjIERlZmluZSBydWxlcw0KDQpDcmVhdGUgY29sdW1uIGZvciBmaXJzdCB2YWxpZGF0aW9uIGJhc2VkIG9uIGRpZmZlcmVudCBpbmRpY2F0b3JzLCB3aGVyZSAid3JvbmciIGlzIG5vdGVkIHdoZW4gdGhlIHZhbGlkYXRpb24gcnVsZSBpcyBub3QgbWV0LiBJbmNsdWRlIEVVTklTMSBjb25mdXNpb25zLg0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JSBjb3VudChFVU5JU2FfMSwgRVVOSVMxX2NvbmZfdHlwZSkNCmBgYA0KDQpEZWZpbmUgcnVsZXM6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgPC0NCiAgZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogIG11dGF0ZSgNCiAgICB2YWxpZF8xX05EV0kgPSBjYXNlX3doZW4oDQogICAgICAjIFBvaW50cyB0aGF0IGFyZSBiYXNpY2FsbHkgd2F0ZXINCiAgICAgIE5EV0lfbWF4ID4gMC4zIH4gIndyb25nIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICB2YWxpZF8xX0NIID0gY2FzZV93aGVuKA0KICAgICAgIyBUIHBvaW50cyB3aXRoIGxvdyBDSA0KICAgICAgRVVOSVNhXzEgPT0gIlQiICYgY2Fub3B5X2hlaWdodCA8IDggfiAid3JvbmciLA0KICAgICAgIyBTIHBvaW50cyB3aXRoIGxvdyBDSA0KICAgICAgRVVOSVNhXzEgPT0iUyIgJiBjYW5vcHlfaGVpZ2h0IDwgNSB+ICJ3cm9uZyIsDQogICAgICAjIFIgJiBRIHBvaW50cyB3aXRoIGhpZ2ggQ0gNCiAgICAgIEVVTklTYV8xICVpbiUgYygiUiIsICJRIikgJiBjYW5vcHlfaGVpZ2h0ID4gMiB+ICJ3cm9uZyIsDQogICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXyksDQogICAgdmFsaWRfMV9ORFZJID0gY2FzZV93aGVuKA0KICAgICAgIyBUIHBvaW50cyB3aXRoIGxvdyBORFZJX21heA0KICAgICAgRVVOSVNhXzEgPT0gIlQiICYgTkRWSV9tYXggPCAwLjYgfiAid3JvbmciLA0KICAgICAgIyBTLVItUSBwb2ludHMgd2l0aCBsb3cgTkRWSV9tYXgNCiAgICAgIEVVTklTYV8xICVpbiUgYygiUiIsICJTIiwgIlEiKSAmIE5EVklfbWF4IDwgMC4yIH4gIndyb25nIiwNCiAgICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICAjIENvdW50IGhvdyBtYW55IHZhbGlkYXRpb24gcnVsZXMgYXJlIG5vdCBtZXQNCiAgICB2YWxpZF8xX2NvdW50ID0gcm93U3VtcyhhY3Jvc3MoYyh2YWxpZF8xX05EV0ksIHZhbGlkXzFfQ0gsIHZhbGlkXzFfTkRWSSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IC4gPT0gIndyb25nIiksIG5hLnJtID0gVFJVRSksDQogICAgIyBQb2ludHMgd2hlcmUgYXQgbGVhc3QgMSBydWxlIG5vdCBtZXQNCiAgICB2YWxpZF8xID0gaWZfZWxzZSh2YWxpZF8xX2NvdW50ID4gMCwgIkF0IGxlYXN0IDEgcnVsZSBicm9rZW4iLA0KICAgICAgICAgICAgICAgICAgICAgICJObyBydWxlcyBicm9rZW4gc28gZmFyIikNCiAgICApDQpgYGANCg0KIyMgUGxvdHMgZmlyc3QgdmFsaWRhdGlvbg0KDQpgYGB7cn0NCmdncGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCU+JQ0KICAgICAgICAgbXV0YXRlKHJ1bGVzX2Jyb2tlbiA9IGNhc2Vfd2hlbigNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgfiAiTkRXSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciIH4gIk5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9ORFZJID09ICJ3cm9uZyJ+ICJORFdJICsgTkRWSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMiAmDQogICAgICAgICAgICAgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyJ+ICJORFdJICsgQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRWSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAzIH4gIk5EV0kgKyBORFZJICsgQ0giLA0KICAgICAgICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICAgICAgKSksIA0KICAgICAgIGFlcyh4ID0gdmFsaWRfMV9jb3VudCwgZmlsbCA9IHJ1bGVzX2Jyb2tlbikpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJOdW1iZXIgb2YgYnJva2VuIHJ1bGVzIikNCmBgYA0KDQpgYGB7cn0NCmRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICAgICAgICAgbXV0YXRlKHJ1bGVzX2Jyb2tlbiA9IGNhc2Vfd2hlbigNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAxICYgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgfiAiTkRXSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMSAmIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciIH4gIk5EVkkiLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDEgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyIgfiAiQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRXSSA9PSAid3JvbmciICYgdmFsaWRfMV9ORFZJID09ICJ3cm9uZyJ+ICJORFdJICsgTkRWSSIsDQogICAgICAgICAgIHZhbGlkXzFfY291bnQgPT0gMiAmDQogICAgICAgICAgICAgdmFsaWRfMV9ORFdJID09ICJ3cm9uZyIgJiB2YWxpZF8xX0NIID09ICJ3cm9uZyJ+ICJORFdJICsgQ0giLA0KICAgICAgICAgICB2YWxpZF8xX2NvdW50ID09IDIgJg0KICAgICAgICAgICAgIHZhbGlkXzFfTkRWSSA9PSAid3JvbmciICYgdmFsaWRfMV9DSCA9PSAid3JvbmcifiAiTkRWSSArIENIIiwNCiAgICAgICAgICAgdmFsaWRfMV9jb3VudCA9PSAzIH4gIk5EV0kgKyBORFZJICsgQ0giLA0KICAgICAgICAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICAgICAgICAgKSkgJT4lDQogIGNvdW50KHJ1bGVzX2Jyb2tlbiwgRVVOSVMxX2NvbmZfdHlwZSkNCmBgYA0KDQpQcm9wb3J0aW9uIG9mIG9ic2VydmF0aW9ucyBub3QgdmFsaWRhdGVkIChzbyBmYXIpOg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lIGZpbHRlcih2YWxpZF8xX2NvdW50ID4gMCkpLw0KICBucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsKQ0KYGBgDQoNCkJ1dCBiZSBhd2FyZSB0aGF0IHRoZXJlIGFyZSBzdGlsbCBNQU5ZIG1pc3NpbmcgUlMgZGF0YS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogICAgICAgICBtdXRhdGUoZGlmZl9HUFMgPSBpZl9lbHNlKA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCAhPSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgICAgaXMubmEoYExvY2F0aW9uIG1ldGhvZGApLCAibm8iLCAieWVzIikpLCANCiAgICAgICBhZXMoeCA9IGRpZmZfR1BTLCBmaWxsID0gdmFsaWRfMSkpICsNCiAgZ2VvbV9iYXIoKSArIGxhYnMoeCA9ICJEaWZmZXJlbnRpYWwgR1BTIikNCmdncGxvdChkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiAgICAgICAgIG11dGF0ZShHUFMgPSBjYXNlX3doZW4oDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIH4gInllcyIsDQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIgfiAieWVzIiwNCiAgICAgICAgICAgaXMubmEoYExvY2F0aW9uIG1ldGhvZGApIH4gIm5vIiwNCiAgICAgICAgICAgVFJVRSB+ICJubyINCiAgICAgICAgICkpLCANCiAgICAgICBhZXMoeCA9IEdQUywgZmlsbCA9IHZhbGlkXzEpKSArDQogIGdlb21fYmFyKCkgKyBsYWJzKHggPSAiR1BTIikNCmBgYA0KDQpQb2ludHMgd2l0aCBhbnkgcnVsZSBicm9rZW4gYW5kIGNvbmZ1c2lvbiBiZXR3ZWVuIEVVTklTOg0KDQpgYGB7cn0NCm5yb3coZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1RfdGVycmVzdHJpYWwgJT4lDQogICAgICAgZmlsdGVyKEVVTklTMV9jb25mID09IFQgJiB2YWxpZF8xX2NvdW50ID4gMCkpDQpgYGANCg0KQ29udmVydCB0byBzaHAgdG8gbG9vayBhdCB0aGVzZSBpbiBHSVM6DQoNCmBgYHtyfQ0KIyBzdF93cml0ZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiMgICAgICAgICAgICBmaWx0ZXIoRVVOSVMxX2NvbmYgPT0gVCAmIHZhbGlkXzFfY291bnQgPiAwKSAlPiUNCiMgICAgICAgICAgICBzdF9hc19zZihjb29yZHMgPSBjKCJMb25fdXBkYXRlZCIsICJMYXRfdXBkYXRlZCIpLCBjcnMgPSA0MzI2KSwNCiMgICAgICAgICAgIkM6L0dJUy9NT1RJVkFURS9zaGFwZWZpbGVzL3Jlc3Vydl9ub3RfdmFsX0VVTklTX2NvbmYuc2hwIikNCmBgYA0KDQpDaGVja2VkIGFuZCB5ZXMNCg0KSG93IG1hbnkgcG9pbnRzIHdpdGggZGlmZmVyZW50aWFsIEdQUyB0aGF0IGhhdmUgYXQgbGVhc3QgMSBydWxlIGJyb2tlbj8NCg0KYGBge3J9DQpucm93KGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJBdCBsZWFzdCAxIHJ1bGUgYnJva2VuIikpDQpgYGANCg0KQ29udmVydCB0byBzaHAgdG8gbG9vayBhdCB0aGVzZSBpbiBHSVM6DQoNCmBgYHtyfQ0KIyBzdF93cml0ZShkYl9yZXN1cnZfUlNfc2hvcnRfUExPVF90ZXJyZXN0cmlhbCAlPiUNCiMgICAgICAgICAgICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgJg0KIyAgICAgICAgICAgICAgICAgICAgIHZhbGlkXzEgPT0gIkF0IGxlYXN0IDEgcnVsZSBicm9rZW4iKSAlPiUNCiMgICAgICAgICAgICBzdF9hc19zZihjb29yZHMgPSBjKCJMb25fdXBkYXRlZCIsICJMYXRfdXBkYXRlZCIpLCBjcnMgPSA0MzI2KSwNCiMgICAgICAgICAgIkM6L0dJUy9NT1RJVkFURS9zaGFwZWZpbGVzL3Jlc3Vydl9ub3RfdmFsX2RpZmZfR1BTLnNocCIpDQpgYGANCg0KIyBNYXBzDQoNCiMjIFBvaW50cyBHUFMNCg0KYGBge3J9DQojIExvYWQgd29ybGQgYm91bmRhcmllcw0Kd29ybGQgPC0gbmVfY291bnRyaWVzKHNjYWxlID0gIm1lZGl1bSIsIHJldHVybmNsYXNzID0gInNmIikNCg0KIyBDYWxjdWxhdGUgdGhlIGV4dGVudCBvZiB0aGUgcG9pbnRzDQpwb2ludHNfR1BTX2V4dGVudCA8LSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogIGZpbHRlcihgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBkaWZmZXJlbnRpYWwgR1BTIiB8DQogICAgICAgICAgIGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIEdQUyIpICU+JQ0KICBzdW1tYXJpc2UobG9uX21pbiA9IG1pbihMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxvbl9tYXggPSBtYXgoTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWluID0gbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21heCA9IG1heChMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSkNCg0KIyBBZGQgcGFkZGluZyB0byB0aGUgZXh0ZW50IChhZGp1c3QgYXMgbmVlZGVkKQ0KcGFkZGluZyA8LSAyICAjIEFkanVzdCBwYWRkaW5nIHRvIHlvdXIgcHJlZmVyZW5jZQ0KeF9saW1pdHMgPC0gYyhwb2ludHNfR1BTX2V4dGVudCRsb25fbWluIC0gcGFkZGluZywNCiAgICAgICAgICAgICAgcG9pbnRzX0dQU19leHRlbnQkbG9uX21heCArIHBhZGRpbmcpDQp5X2xpbWl0cyA8LSBjKHBvaW50c19HUFNfZXh0ZW50JGxhdF9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfR1BTX2V4dGVudCRsYXRfbWF4ICsgcGFkZGluZykNCg0KIyBDcmVhdGUgdGhlIHpvb21lZCBtYXANCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gd29ybGQsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogICAgICAgICAgICAgICBmaWx0ZXIoRVVOSVNhXzEgJWluJSBjKCJUIiwgIlIiLCAiUyIsICJRIikpICU+JQ0KICAgICAgICAgICAgICAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogICAgICAgICAgICAgICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgfA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiKSwNCiAgICAgICAgICAgICBhZXMoeCA9IExvbl91cGRhdGVkLCB5ID0gTGF0X3VwZGF0ZWQsIGNvbG9yID0gRVVOSVNhXzEpLA0KICAgICAgICAgICAgIHNpemUgPSAxKSArDQogIGNvb3JkX3NmKHhsaW0gPSB4X2xpbWl0cywgeWxpbSA9IHlfbGltaXRzKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCk51bWJlciBvZiBHUFMgcG9pbnRzIGJ5IENvdW50cnk6DQoNCmBgYHtyfQ0KZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBmaWx0ZXIoYExvY2F0aW9uIG1ldGhvZGAgPT0gIkxvY2F0aW9uIHdpdGggZGlmZmVyZW50aWFsIEdQUyIgfA0KICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiKSAlPiUNCiAgY291bnQoQ291bnRyeSkNCmBgYA0KDQojIyBQb2ludHMgUmVTdXJ2ZXkNCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgZXh0ZW50IG9mIHRoZSBwb2ludHMNCnBvaW50c19yZXN1cnZleV9leHRlbnQgPC0gZGJfcmVzdXJ2X1JTX3Nob3J0X1BMT1QgJT4lDQogIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogIGZpbHRlcihTMl9kYXRhID09IFQgfCBMYW5kc2F0X2RhdGEgPT0gVCApICU+JQ0KICBzdW1tYXJpc2UobG9uX21pbiA9IG1pbihMb25fdXBkYXRlZCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIGxvbl9tYXggPSBtYXgoTG9uX3VwZGF0ZWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBsYXRfbWluID0gbWluKExhdF91cGRhdGVkLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgbGF0X21heCA9IG1heChMYXRfdXBkYXRlZCwgbmEucm0gPSBUUlVFKSkNCg0KIyBBZGQgcGFkZGluZyB0byB0aGUgZXh0ZW50IChhZGp1c3QgYXMgbmVlZGVkKQ0KcGFkZGluZyA8LSAyICAjIEFkanVzdCBwYWRkaW5nIHRvIHlvdXIgcHJlZmVyZW5jZQ0KeF9saW1pdHMgPC0gYyhwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxvbl9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxvbl9tYXggKyBwYWRkaW5nKQ0KeV9saW1pdHMgPC0gYyhwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxhdF9taW4gLSBwYWRkaW5nLA0KICAgICAgICAgICAgICBwb2ludHNfcmVzdXJ2ZXlfZXh0ZW50JGxhdF9tYXggKyBwYWRkaW5nKQ0KDQojIENyZWF0ZSB0aGUgem9vbWVkIG1hcA0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSB3b3JsZCwgZmlsbCA9ICJsaWdodGJsdWUiLCBjb2xvciA9ICJncmF5IikgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgICAgICAgICAgICAgIGZpbHRlcihFVU5JU2FfMSAlaW4lIGMoIlQiLCAiUiIsICJTIiwgIlEiKSkgJT4lDQogICAgICAgICAgICAgICBmaWx0ZXIoUzJfZGF0YSA9PSBUIHwgTGFuZHNhdF9kYXRhID09IFQgKSwNCiAgICAgICAgICAgICBhZXMoeCA9IExvbl91cGRhdGVkLCB5ID0gTGF0X3VwZGF0ZWQsIGNvbG9yID0gRVVOSVNhXzEpLA0KICAgICAgICAgICAgIHNpemUgPSAxKSArDQogIGNvb3JkX3NmKHhsaW0gPSB4X2xpbWl0cywgeWxpbSA9IHlfbGltaXRzKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCk51bWJlciBvZiBSZVN1cnZleSBwb2ludHMgYnkgQ291bnRyeToNCg0KYGBge3J9DQpkYl9yZXN1cnZfUlNfc2hvcnRfUExPVCAlPiUNCiAgZmlsdGVyKEVVTklTYV8xICVpbiUgYygiVCIsICJSIiwgIlMiLCAiUSIpKSAlPiUNCiAgZmlsdGVyKFMyX2RhdGEgPT0gVCB8IExhbmRzYXRfZGF0YSA9PSBUICkgJT4lDQogIGNvdW50KENvdW50cnkpDQpgYGANCg0KIyBEaXN0cmlidXRpb25zIGZyb20gR1BTIHBvaW50cyB3aXRob3V0IHJ1bGVzIGJyb2tlbiBzbyBmYXINCg0KQ3JlYXRlIHRpYmJsZSB3aXRoIGRpZmZlcmVudGlhbCBHUFMgcG9pbnRzIHdpdGhvdXQgcnVsZXMgYnJva2VuIHNvIGZhcjoNCg0KYGBge3J9DQphbGxfR1BTX3ZhbGlkIDwtIGRiX3Jlc3Vydl9SU19zaG9ydF9QTE9UX3RlcnJlc3RyaWFsICU+JQ0KICBmaWx0ZXIoKGBMb2NhdGlvbiBtZXRob2RgID09ICJMb2NhdGlvbiB3aXRoIGRpZmZlcmVudGlhbCBHUFMiIHwgDQogICAgICAgICAgICBgTG9jYXRpb24gbWV0aG9kYCA9PSAiTG9jYXRpb24gd2l0aCBHUFMiICkgJg0KICAgICAgICAgICB2YWxpZF8xID09ICJObyBydWxlcyBicm9rZW4gc28gZmFyIikgDQpgYGANCg0KIyMgTkRWSSwgTkRNSSwgTkRXSSwgU0FWSSBhbmQgRVZJDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJORFZJX3A5MCIsICJORFZJX21pbiIsICJORFZJX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRWSSBtYXgiLCAiTkRWSSBwOTAiLCAiTkRWSSBtaW4iLCAiTkRWSSBwMTAiKSkNCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiTkRNSV9tYXgiLCAiTkRNSV9wOTAiLCAiTkRNSV9taW4iLCAiTkRNSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5ETUkgbWF4IiwgIk5ETUkgcDkwIiwgIk5ETUkgbWluIiwgIk5ETUkgcDEwIikpDQpkaXN0cl9wbG90KGFsbF9HUFNfdmFsaWQsDQogICAgICAgICAgIGMoIk5EV0lfbWF4IiwgIk5EV0lfcDkwIiwgIk5EV0lfbWluIiwgIk5EV0lfcDEwIiksIA0KICAgICAgICAgICBjKCJORFdJIG1heCIsICJORFdJIHA5MCIsICJORFdJIG1pbiIsICJORFdJIHAxMCIpKQ0KZGlzdHJfcGxvdChhbGxfR1BTX3ZhbGlkLA0KICAgICAgICAgICBjKCJTQVZJX21heCIsICJTQVZJX3A5MCIsICJTQVZJX21pbiIsICJTQVZJX3AxMCIpLCANCiAgICAgICAgICAgYygiU0FWSSBtYXgiLCAiU0FWSSBwOTAiLCAiU0FWSSBtaW4iLCAiU0FWSSBwMTAiKSkNCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCAlPiUNCiAgICAgICAgICAgICBmaWx0ZXIoRVZJX21heCA8PSAxKSAlPiUNCiAgICAgICAgICAgICBmaWx0ZXIoRVZJX21pbiA+PSAtMSAmIEVWSV9taW4gPD0gMSksDQogICAgICAgICAgIGMoIkVWSV9tYXgiLCAiRVZJX3A5MCIsICJFVklfbWluIiwgIkVWSV9wMTAiKSwgDQogICAgICAgICAgIGMoIkVWSSBtYXgiLCAiRVZJIHA5MCIsICJFVkkgbWluIiwgIkVWSSBwMTAiKSkNCmBgYA0KDQojIyBDSA0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwgImNhbm9weV9oZWlnaHQiLCAiQ2Fub3B5IGhlaWdodCAobSkiKQ0KYGBgDQoNCiMjIFBoZW5vbG9neQ0KDQpgYGB7cn0NCmRpc3RyX3Bsb3QoYWxsX0dQU192YWxpZCwNCiAgICAgICAgICAgYygiU09TX0RPWSIsIlBlYWtfRE9ZIiwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICJORFZJX2F0X1NPUyIsICJORFZJX2F0X1BlYWsiLCAiTkRWSV9hdF9FT1MiLA0KICAgICAgICAgICAgICJkaWZmX1BlYWtfU09TIiwiZGlmZl9QZWFrX0VPUyIsICJTZWFzb25fTGVuZ3RoIiksDQogICAgICAgICAgIGMoIlNPUyBET1kiLCAiUGVhayBET1kiLCAiRU9TIERPWSIsDQogICAgICAgICAgICAgIk5EVkkgYXQgU09TIiwgIk5EVkkgYXQgUGVhayIsICJORFZJIGF0IEVPUyIsDQogICAgICAgICAgICAgIkRpZmZlcmVuY2UgUGVhay1TT1MiLCAiRGlmZmVyZW5jZSBQZWFrLUVPUyIsICJTZWFzb24gTGVuZ3RoIikpDQpgYGANCg0KIyBHUFMgdmFsaWQgcG9pbnRzIGFib3ZlIHAyMCBvZiBORFZJX21heCBhbmQgTkRNSV9taW4gZm9yIGVhY2ggaGFiaXRhdA0KDQojIEhFUkUhIA0KDQpDaG9zZW4gTkRWSV9taW4gYmVjYXVzZSBpdCB3YXMgaW1wb3J0YW50IGluIFJGIG1vZGVscywgYnV0IGxldCdzIHNlZSB3aXRoIG5ldyBkYXRhIQ0KDQpgYGB7cn0NCnBlcmNlbnRpbGVzX2FsbF9HUFMgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZ3JvdXBfYnkoRVVOSVNhXzEpICU+JQ0KICBzdW1tYXJpemUocGVyY2VudGlsZV8yMF9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCBwcm9icyA9IDAuMjAsIG5hLnJtID0gVCksDQogICAgICAgICAgICBwZXJjZW50aWxlXzIwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIHByb2JzID0gMC4yMCwgbmEucm0gPSBUKSkNCg0KYWxsX0dQU192YWxpZCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBsZWZ0X2pvaW4ocGVyY2VudGlsZXNfYWxsX0dQUywgYnkgPSAiRVVOSVNhXzEiKSAlPiUNCiAgbXV0YXRlKGNhdGVnb3J5X05EVklfbWF4ID0gY2FzZV93aGVuKA0KICAgIE5EVklfbWF4IDwgcGVyY2VudGlsZV8yMF9ORFZJX21heCB+ICJiZWxvd18yMHRoIiwNCiAgICBORFZJX21heCA+PSBwZXJjZW50aWxlXzIwX05EVklfbWF4IH4gImFib3ZlXzIwdGgiKSwNCiAgY2F0ZWdvcnlfTkRNSV9taW4gPSBjYXNlX3doZW4oDQogICAgTkRNSV9taW4gPCBwZXJjZW50aWxlXzIwX05ETUlfbWluIH4gImJlbG93XzIwdGgiLA0KICAgIE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gfiAiYWJvdmVfMjB0aCIpKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFsbF9HUFNfdmFsaWQsDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9IE5EVklfbWF4KSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjYXRlZ29yeV9ORFZJX21heCksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgbGFicyh5ID0gIk5EVkkgbWF4IiwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJlbG93XzIwdGgiID0gImdyZXkiLCAiYWJvdmVfMjB0aCIgPSAibGlnaHRibHVlIikpICsNCiAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFsbF9HUFNfdmFsaWQsDQogICAgICAgYWVzKHggPSBFVU5JU2FfMV9kZXNjciwgeSA9IE5ETUlfbWluKSkgKw0KICBnZW9tX2ZsYXRfdmlvbGluKHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMiwgeSA9IDApLCBhbHBoYSA9IDAuOCwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBjYXRlZ29yeV9ORE1JX21pbiksDQogICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjE1KSwgc2l6ZSA9IDEsIGFscGhhID0gMC4yNSkgKw0KICBnZW9tX2JveHBsb3Qod2lkdGggPSAwLjIsIG91dGxpZXIuc2hhcGUgPSBOQSwgYWxwaGEgPSAwLjUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55ID0gbWVhbiwgZ2VvbSA9ICJwb2ludCIsIHNoYXBlID0gMjAsIHNpemUgPSAxKSArDQogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9IGZ1bmN0aW9uKHgpIGRhdGEuZnJhbWUoeSA9IG1heCh4KSArIDAuMSwgbGFiZWwgPSBsZW5ndGgoeCkpLA0KICAgICAgICAgICAgICAgZ2VvbSA9ICJ0ZXh0IiwgYWVzKGxhYmVsID0gLi5sYWJlbC4uKSwgdmp1c3QgPSAwLjUpICsNCiAgbGFicyh5ID0gIk5ETUkgbWluIiwgeCA9ICJFVU5JUyBsZXZlbCAxIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFLCBjb2xvciA9IEZBTFNFKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzID0gZnVuY3Rpb24oeCkgc3RyX3dyYXAoeCwgd2lkdGggPSAxNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImJlbG93XzIwdGgiID0gImdyZXkiLCAiYWJvdmVfMjB0aCIgPSAibGlnaHRibHVlIikpICsNCiAgdGhlbWVfYncoKSArIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMgUkYgbW9kZWxzDQoNClVzaW5nIHRoZSBjb25kaXRpb25hbCBpbmZlcmVuY2UgdmVyc2lvbiBvZiByYW5kb20gZm9yZXN0IChjZm9yZXN0IGluIHBhY2thZ2UgcGFydHkpLiBTdWdnZXN0ZWQgaWYgdGhlIGRhdGEgYXJlIGhpZ2hseSBjb3JyZWxhdGVkLiBDZm9yZXN0IGlzIG1vcmUgc3RhYmxlIGluIGRlcml2aW5nIHZhcmlhYmxlIGltcG9ydGFuY2UgdmFsdWVzIGluIHRoZSBwcmVzZW5jZSBvZiBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMsIHRodXMgcHJvdmlkaW5nIGJldHRlciBhY2N1cmFjeSBpbiBjYWxjdWxhdGluZyB2YXJpYWJsZSBpbXBvcnRhbmNlIChyZWYgYmVsb3cpLg0KDQpIb3Rob3JuLCBULiwgSG9ybmlrLCBLLiBhbmQgWmVpbGVpcywgQS4gKDIwMDYpIFVuYmlhc2VkIFJlY3Vyc2l2ZSBQb3J0aW9uaW5nOiBBIENvbmRpdGlvbmFsIEluZmVyZW5jZSBGcmFtZXdvcmsuIEpvdXJuYWwgb2YgQ29tcHV0YXRpb25hbCBhbmQgR3JhcGhpY2FsIFN0YXRpc3RpY3MsIDE1LCA2NTEtDQo2NzQuIGh0dHA6Ly9keC5kb2kub3JnLzEwLjExOTgvMTA2MTg2MDA2WDEzMzkzMw0KDQojIyBBbGwgR1BTIHBvaW50cw0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEwIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzMCA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGEwKSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhMCkpDQp0cmFpbl9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFt0cmFpbl9pbmRpY2VzMCwgXQ0KdGVzdF9kYXRhMCA8LSBmaWx0ZXJlZF9kYXRhMFstdHJhaW5faW5kaWNlczAsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhMCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0MCA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QwIDwtIHByZWRpY3QocmZfY2ZvcmVzdDAsIG5ld2RhdGEgPSB0ZXN0X2RhdGEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MCwgdGVzdF9kYXRhMCRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QwLCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0MCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3QwLCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3QwLCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDAuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0MF9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3QwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDApDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3QwX2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDAsIG5ld2RhdGEgPSB0ZXN0X2RhdGEwLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGEwJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2MwIDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jMA0KYGBgDQoNCiMjIFJFVklTRSBGUk9NIEhFUkU6IEFsbCBHUFMgcG9pbnRzIGFib3ZlIHAyMA0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyBhYm92ZSBwMjAgb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgc2VsZWN0KC1wZXJjZW50aWxlXzIwX05EVklfbWF4LCAtcGVyY2VudGlsZV8yMF9ORE1JX21pbikNCmBgYA0KDQpgYGB7cn0NCnBlcmNlbnRpbGVzIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC4yMCwgbmEucm0gPSBUKSwNCiAgICBwZXJjZW50aWxlXzIwX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuMjAsIG5hLnJtID0gVCksDQogICAgcGVyY2VudGlsZV84MF9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCAwLjgwLCBuYS5ybSA9IFQpLA0KICAgIHBlcmNlbnRpbGVfODBfTkRNSV9taW4gPSBxdWFudGlsZShORE1JX21pbiwgMC44MCwgbmEucm0gPSBUKQ0KICAgICkNCg0KIyBKb2luIHRoZSBwZXJjZW50aWxlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCBkYXRhDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihwZXJjZW50aWxlcywgYnkgPSAiRVVOSVNhXzEiKQ0KDQojIEZpbHRlciByb3dzIGFib3ZlIHRoZSAyMHRoIHBlcmNlbnRpbGUgZm9yIGJvdGggdmFyaWFibGVzIGZvciBlYWNoIGNhdGVnb3J5IG9mIEVVTklTYV8xDQpmaWx0ZXJlZF9kYXRhMSA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoDQogICAgTkRWSV9tYXggPj0gcGVyY2VudGlsZV8yMF9ORFZJX21heCAmIE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4NCiAgICApICU+JQ0KICBmaWx0ZXIoIWlzLm5hKE5EVklfbWF4KSAmICFpcy5uYShORE1JX21heCkgJiAhaXMubmEoTkRXSV9tYXgpICYNCiAgICAgICAgICAgIWlzLm5hKFNBVklfbWF4KSAmICFpcy5uYShFVklfbWF4KSAmICFpcy5uYShORFZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoTkRNSV9taW4pICYgIWlzLm5hKE5EV0lfbWluKSAmICFpcy5uYShTQVZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoRVZJX21pbikpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczEgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMSksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTEpKQ0KdHJhaW5fZGF0YTEgPC0gZmlsdGVyZWRfZGF0YTFbdHJhaW5faW5kaWNlczEsIF0NCnRlc3RfZGF0YTEgPC0gZmlsdGVyZWRfZGF0YTFbLXRyYWluX2luZGljZXMxLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTEgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCkludmVzdGlnYXRlIHBhY2thZ2UgZ2dwYXJ0eSAoZS5nLiBhdXRvcGxvdCBmdW5jdGlvbiwgYW5kIG1vcmUpLg0KDQpUTy1ETzogDQpDaG9vc2UgdGhlIGh5cGVycGFyYW1ldGVyIG10cnkgYmFzZWQgb24gdGhlIHNxdWFyZSByb290IG9mIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcyAoSGFzdGllIGV0IGFsLiwgMjAwOSktDQoNCkhhc3RpZSwgVC4sIFRpYnNoaXJhbmksIFIuLCAmIEZyaWVkbWFuLCBKLiAoMjAwOSkuIFRoZSBlbGVtZW50cyBvZiBzdGF0aXN0aWNhbA0KbGVhcm5pbmc6IERhdGEgbWluaW5nLCBpbmZlcmVuY2UsIGFuZCBwcmVkaWN0aW9uLiBTcHJpbmdlciBTY2llbmNlICYNCkJ1c2luZXNzIE1lZGlhLg0KDQpNYXliZSBUT19ETzoNCldlIHZhcmlhdGVkIG50cmVlIGZyb20gNTAgdG8gODAwIGluIHN0ZXBzIG9mIDUwLCBsZWF2aW5nIG10cnkgY29uc3RhbnQgYXQgMi4gVGlzIHBhcmFtZXRlciB2YXJpYXRpb24gc2hvd2VkIHRoYXQgbnRyZWU9NTAwIHdhcyBvcHRpbWFsLCB3aGlsZSBoaWdoZXIgbnRyZWUgbGVkIHRvIG5vIGZ1cnRoZXIgbW9kZWwgaW1wcm92ZW1lbnQgKFN1cHBsZW1lbnRhcnkgRmlnLiBTMTApLiBTdWJzZXF1ZW50bHksIHRoZSBoeXBlcnBhcmFtZXRlciBtdHJ5IHdhcyB2YXJpZWQgZnJvbSAyIHRvIDggd2l0aCBjb25zdGFudCBudHJlZT01MDAuIEhlcmUsIG10cnk9MyBsZWQgdG8gdGhlIGJlc3QgcmVzdWx0cyBpbiBhbG1vc3QgYWxsIGNhc2VzIChTdXBwbGVtZW50YXJ5IEZpZy4gUzExKS4gQ29uc2VxdWVudGx5LCB3ZSBjaG9zZSBudHJlZT01MDAgYW5kIG10cnk9MyBmb3Igb3VyIG1haW4gYW5hbHlzaXMgYWNyb3NzIGFsbCBzdHVkeSBzaXRlcy4NCg0KYGBge3J9DQpyZl9jZm9yZXN0MSA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QxIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0MSwgdGVzdF9kYXRhMSRFVU5JU2FfMSkNCmBgYA0KDQpTdXJyb2dhdGVUcmVlIC0tPiBkb2VzIG5vdCB3b3JrDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3QxIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDEsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDEsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0MS5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QxX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MSkNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDFfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClRyZWUgVmlzdWFsaXphdGlvbg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGEgc2luZ2xlIGNvbmRpdGlvbmFsIGluZmVyZW5jZSB0cmVlIHVzaW5nIGN0cmVlDQpzaW5nbGVfdHJlZTEgPC0gY3RyZWUoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKyBORE1JX21pbiArDQogICAgICAgICAgICAgICAgICAgICAgIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsgRVZJX21pbiArIFNBVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LA0KICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGExKQ0KDQojIFBsb3QgdGhlIHNpbmdsZSB0cmVlIHVzaW5nDQphdXRvcGxvdChzaW5nbGVfdHJlZTEpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMSA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzENCmBgYA0KDQoNCiMjIEFsbCBHUFMgcG9pbnRzIHdpdGhpbiBJUSByYW5nZQ0KDQpGaWx0ZXIgdGhlIGRhdGEgdG8gZ2V0IG9ubHkgR1BTLXBvaW50cyB3aXRoaW4gSVEgcmFuZ2Ugb2YgTkRWSV9tYXggYW5kIE5ETUlfbWluLg0KDQpgYGB7cn0NCklRX3JhbmdlcyA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBncm91cF9ieShFVU5JU2FfMSkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBRMV9ORFZJX21heCA9IHF1YW50aWxlKE5EVklfbWF4LCAwLjI1LCBuYS5ybSA9IFQpLA0KICAgIFExX05ETUlfbWluID0gcXVhbnRpbGUoTkRNSV9taW4sIDAuMjUsIG5hLnJtID0gVCksDQogICAgUTNfTkRWSV9tYXggPSBxdWFudGlsZShORFZJX21heCwgMC43NSwgbmEucm0gPSBUKSwNCiAgICBRM19ORE1JX21pbiA9IHF1YW50aWxlKE5ETUlfbWluLCAwLjc1LCBuYS5ybSA9IFQpLA0KICAgIElRUl9ORFZJX21heCA9IElRUihORFZJX21heCwgbmEucm0gPSBUUlVFKSwNCiAgICBJUVJfTkRNSV9taW4gPSBJUVIoTkRNSV9taW4sIG5hLnJtID0gVFJVRSkNCiAgICApDQoNCiMgSm9pbiB0aGUgSVEgcmFuZ2VzIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRhdGENCmFsbF9HUFNfdmFsaWQgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgbGVmdF9qb2luKElRX3JhbmdlcywgYnkgPSAiRVVOSVNhXzEiKQ0KDQojIEZpbHRlciByb3dzIHdpdGhpbiB0aGUgSVFSIHJhbmdlIGZvciBib3RoIHZhcmlhYmxlcw0KZmlsdGVyZWRfZGF0YTIgPC0gYWxsX0dQU192YWxpZCAlPiUNCiAgZmlsdGVyKA0KICAgIChORFZJX21heCA+PSBRMV9ORFZJX21heCAmIE5EVklfbWF4IDw9IFEzX05EVklfbWF4KSAmDQogICAgKE5ETUlfbWluID49IFExX05ETUlfbWluICYgTkRNSV9taW4gPD0gUTNfTkRNSV9taW4pDQogICAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShORFZJX21heCkgJiAhaXMubmEoTkRNSV9tYXgpICYgIWlzLm5hKE5EV0lfbWF4KSAmDQogICAgICAgICAgICFpcy5uYShTQVZJX21heCkgJiAhaXMubmEoRVZJX21heCkgJiAhaXMubmEoTkRWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKE5ETUlfbWluKSAmICFpcy5uYShORFdJX21pbikgJiAhaXMubmEoU0FWSV9taW4pICYNCiAgICAgICAgICAgIWlzLm5hKEVWSV9taW4pKSAlPiUNCiAgbXV0YXRlKEVVTklTYV8xID0gYXMuZmFjdG9yKEVVTklTYV8xKSkgJT4lDQogIGZpbHRlcihFVklfbWF4IDw9IDEgJiBFVklfbWluID49IC0xKQ0KYGBgDQoNClNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzLg0KDQpgYGB7cn0NCnRyYWluX2luZGljZXMyIDwtIHNhbXBsZSgxOm5yb3coZmlsdGVyZWRfZGF0YTIpLCAwLjcgKiBucm93KGZpbHRlcmVkX2RhdGEyKSkNCnRyYWluX2RhdGEyIDwtIGZpbHRlcmVkX2RhdGEyW3RyYWluX2luZGljZXMyLCBdDQp0ZXN0X2RhdGEyIDwtIGZpbHRlcmVkX2RhdGEyWy10cmFpbl9pbmRpY2VzMiwgXQ0KYGBgDQoNCk51bWJlciBvZiBwb2ludHMgcGVyIGNhdGVnb3J5IGZvciBmaWx0ZXJlZCBkYXRhOg0KDQpgYGB7cn0NCmZpbHRlcmVkX2RhdGEyICU+JSBjb3VudChFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnJmX2Nmb3Jlc3QyIDwtIHBhcnR5OjpjZm9yZXN0KEVVTklTYV8xIH4gTkRWSV9tYXggKyBORFZJX21pbiArIE5ETUlfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkRNSV9taW4gKyBORFdJX21heCArIE5EV0lfbWluICsgRVZJX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVWSV9taW4gKyBTQVZJX21heCArIFNBVklfbWluICsgY2Fub3B5X2hlaWdodCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fZGF0YTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250cm9scyA9IGNmb3Jlc3RfY29udHJvbCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyeSA9IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbXRyeSA9IHNxcnQoMTEpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRGVmYXVsdCBtdHJ5ID0gNQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEJhZ2dpbmc6IG10cnkgPSBOVUxMDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgb3IgPSBudW1iZXIgb2YgaW5wdXQgdmFyaWFibGVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwKSAjIERlZmF1bHQsIHRyeSBpbmNyZWFzaW5nDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApIA0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfcmZfY2ZvcmVzdDIgPC0gcHJlZGljdChyZl9jZm9yZXN0MiwgbmV3ZGF0YSA9IHRlc3RfZGF0YTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9PQiA9IFRSVUUsIHR5cGUgPSAicmVzcG9uc2UiKQ0KYGBgDQoNCkNvbmZ1c2lvbiBtYXRyaXg6DQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zX3JmX2Nmb3Jlc3QyLCB0ZXN0X2RhdGEyJEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QyIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDIsIGNvbmRpdGlvbmFsID0gRikgDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnZhcmltcF9yZl9jb25kX2Nmb3Jlc3QyIDwtIHBhcnR5Ojp2YXJpbXAocmZfY2ZvcmVzdDIsIGNvbmRpdGlvbmFsID0gVCkNCiMgY29uZGl0aW9uYWwgPSBUIGFkanVzdHMgZm9yIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHByZWRpY3RvciB2YXJpYWJsZXMNCiMgVGFrZXMgbG9uZyENCnNhdmUodmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDIsIGZpbGUgPSAib2JqZWN0cy92YXJpbXBfcmZfY29uZF9jZm9yZXN0Mi5SZGF0YSIpDQpgYGANCg0KVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90DQoNCmBgYHtyfQ0KdmFyaW1wX3JmX2Nmb3Jlc3QyX2RmIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBuYW1lcyh2YXJpbXBfcmZfY2ZvcmVzdDIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSW1wb3J0YW5jZSA9IHZhcmltcF9yZl9jZm9yZXN0MikNCmdncGxvdCh2YXJpbXBfcmZfY2ZvcmVzdDJfZGYsDQogICAgICAgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gImxpZ2h0Ymx1ZSIpICsNCiAgY29vcmRfZmxpcCgpICsgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIiwgeCA9ICJWYXJpYWJsZXMiLCB5ID0gIkltcG9ydGFuY2UiKQ0KYGBgDQoNClJPQyBjdXJ2ZXM6DQoNCmBgYHtyfQ0KIyBQcmVkaWN0IHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggY2xhc3MNCnByb2JhYmlsaXRpZXMgPC0gcHJlZGljdChyZl9jZm9yZXN0MSwgbmV3ZGF0YSA9IHRlc3RfZGF0YTEsIHR5cGUgPSAicHJvYiIpDQoNCiMgU3RlcCAxOiBDb252ZXJ0IGxpc3Qgb2YgbWF0cmljZXMgdG8gYSBwcm9wZXIgZGF0YSBmcmFtZQ0KcHJvYl9tYXRyaXggPC0gdChzYXBwbHkocHJvYmFiaWxpdGllcywgYXMudmVjdG9yKSkNCmNvbG5hbWVzKHByb2JfbWF0cml4KSA8LSBjKCJRIiwgIlIiLCAiUyIsICJUIikgICMgQWRqdXN0IGlmIG5lZWRlZA0KcHJvYl9kZiA8LSBhcy5kYXRhLmZyYW1lKHByb2JfbWF0cml4KQ0KDQojIFN0ZXAgMjogUHJlcGFyZSBhY3R1YWwgY2xhc3MgbGFiZWxzDQphY3R1YWwgPC0gZmFjdG9yKHRlc3RfZGF0YTEkRVVOSVNhXzEsIGxldmVscyA9IGMoIlEiLCAiUiIsICJTIiwgIlQiKSkNCmNsYXNzZXMgPC0gbGV2ZWxzKGFjdHVhbCkNCg0KIyBTdGVwIDM6IEJpbmFyaXplIGFjdHVhbCBsYWJlbHMNCmFjdHVhbF9iaW4gPC0gbW9kZWwubWF0cml4KH4gYWN0dWFsIC0gMSkNCmNvbG5hbWVzKGFjdHVhbF9iaW4pIDwtIGdzdWIoImFjdHVhbCIsICIiLCBjb2xuYW1lcyhhY3R1YWxfYmluKSkNCg0KIyBTdGVwIDQ6IENvbXB1dGUgUk9DIGRhdGEgZm9yIGVhY2ggY2xhc3Mgd2l0aCBBVUMgaW4gbGFiZWwNCnJvY19kYXRhIDwtIGxhcHBseShjbGFzc2VzLCBmdW5jdGlvbihjbGFzcykgew0KICByb2Nfb2JqIDwtIHJvYyhhY3R1YWxfYmluWywgY2xhc3NdLCBwcm9iX2RmW1tjbGFzc11dKQ0KICBhdWNfdmFsIDwtIHJvdW5kKGF1Yyhyb2Nfb2JqKSwgMykNCiAgZGF0YS5mcmFtZSgNCiAgICBGUFIgPSByZXYocm9jX29iaiRzcGVjaWZpY2l0aWVzKSwNCiAgICBUUFIgPSByZXYocm9jX29iaiRzZW5zaXRpdml0aWVzKSwNCiAgICBDbGFzcyA9IHBhc3RlMChjbGFzcywgIiAoQVVDID0gIiwgYXVjX3ZhbCwgIikiKQ0KICApDQp9KSAlPiUgYmluZF9yb3dzKCkNCg0KIyBTdGVwIDU6IFBsb3QgUk9DIGN1cnZlcyB3aXRoIGdncGxvdDINCnJvYzIgPC0gZ2dwbG90KHJvY19kYXRhLCBhZXMoeCA9IEZQUiwgeSA9IFRQUiwgY29sb3IgPSBDbGFzcykpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgZ2VvbV9hYmxpbmUobGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiZ3JheSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNdWx0aWNsYXNzIFJPQyBDdXJ2ZXMgd2l0aCBBVUMiLA0KICAgIHggPSAiRmFsc2UgUG9zaXRpdmUgUmF0ZSIsDQogICAgeSA9ICJUcnVlIFBvc2l0aXZlIFJhdGUiLA0KICAgIGNvbG9yID0gIkNsYXNzIChBVUMpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQpyb2MyDQpgYGANCg0KIyMgQWxsIEdQUyBwb2ludHMgd2l0aGluIDEuNSAqIElRIHJhbmdlDQoNCkZpbHRlciB0aGUgZGF0YSB0byBnZXQgb25seSBHUFMtcG9pbnRzIHdpdGhpbiAxLjUgKiBJUSByYW5nZSBvZiBORFZJX21heCBhbmQgTkRNSV9taW4uDQoNCmBgYHtyfQ0KIyBGaWx0ZXIgcm93cyB3aXRoaW4gdGhlIDEuNSAqIElRUiByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMNCmZpbHRlcmVkX2RhdGEzIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGZpbHRlcigNCiAgICAoTkRWSV9tYXggPj0gKFExX05EVklfbWF4IC0gMS41ICogSVFSX05EVklfbWF4KSAmIE5EVklfbWF4IDw9IChRM19ORFZJX21heCArIDEuNSAqIElRUl9ORFZJX21heCkpICYNCiAgICAgIChORE1JX21pbiA+PSAoUTFfTkRNSV9taW4gLSAxLjUgKiBJUVJfTkRNSV9taW4pICYgTkRNSV9taW4gPD0gKFEzX05ETUlfbWluICsgMS41ICogSVFSX05ETUlfbWluKSkNCiAgICApICU+JQ0KICBmaWx0ZXIoIWlzLm5hKE5EVklfbWF4KSAmICFpcy5uYShORE1JX21heCkgJiAhaXMubmEoTkRXSV9tYXgpICYNCiAgICAgICAgICAgIWlzLm5hKFNBVklfbWF4KSAmICFpcy5uYShFVklfbWF4KSAmICFpcy5uYShORFZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoTkRNSV9taW4pICYgIWlzLm5hKE5EV0lfbWluKSAmICFpcy5uYShTQVZJX21pbikgJg0KICAgICAgICAgICAhaXMubmEoRVZJX21pbikpICU+JQ0KICBtdXRhdGUoRVVOSVNhXzEgPSBhcy5mYWN0b3IoRVVOSVNhXzEpKSAlPiUNCiAgZmlsdGVyKEVWSV9tYXggPD0gMSAmIEVWSV9taW4gPj0gLTEpDQpgYGANCg0KU3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIHNldHMuDQoNCmBgYHtyfQ0KdHJhaW5faW5kaWNlczMgPC0gc2FtcGxlKDE6bnJvdyhmaWx0ZXJlZF9kYXRhMyksIDAuNyAqIG5yb3coZmlsdGVyZWRfZGF0YTMpKQ0KdHJhaW5fZGF0YTMgPC0gZmlsdGVyZWRfZGF0YTNbdHJhaW5faW5kaWNlczMsIF0NCnRlc3RfZGF0YTMgPC0gZmlsdGVyZWRfZGF0YTNbLXRyYWluX2luZGljZXMzLCBdDQpgYGANCg0KTnVtYmVyIG9mIHBvaW50cyBwZXIgY2F0ZWdvcnkgZm9yIGZpbHRlcmVkIGRhdGE6DQoNCmBgYHtyfQ0KZmlsdGVyZWRfZGF0YTMgJT4lIGNvdW50KEVVTklTYV8xKQ0KYGBgDQoNCmBgYHtyfQ0KcmZfY2ZvcmVzdDMgPC0gcGFydHk6OmNmb3Jlc3QoRVVOSVNhXzEgfiBORFZJX21heCArIE5EVklfbWluICsgTkRNSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBORE1JX21pbiArIE5EV0lfbWF4ICsgTkRXSV9taW4gKyBFVklfbWF4ICsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVZJX21pbiArIFNBVklfbWF4ICsgU0FWSV9taW4gKyBjYW5vcHlfaGVpZ2h0LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2xzID0gY2ZvcmVzdF9jb250cm9sKA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBtdHJ5ID0gc3FydCgxMSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBEZWZhdWx0IG10cnkgPSA1DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQmFnZ2luZzogbXRyeSA9IE5VTEwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvciA9IG51bWJlciBvZiBpbnB1dCB2YXJpYWJsZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApICMgRGVmYXVsdCwgdHJ5IGluY3JlYXNpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkgDQpgYGANCg0KYGBge3J9DQpwcmVkaWN0aW9uc19yZl9jZm9yZXN0MyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QzLCBuZXdkYXRhID0gdGVzdF9kYXRhMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT09CID0gVFJVRSwgdHlwZSA9ICJyZXNwb25zZSIpDQpgYGANCg0KQ29uZnVzaW9uIG1hdHJpeDoNCg0KYGBge3J9DQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbnNfcmZfY2ZvcmVzdDMsIHRlc3RfZGF0YTMkRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDMgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MywgY29uZGl0aW9uYWwgPSBGKSANCmBgYA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDMgPC0gcGFydHk6OnZhcmltcChyZl9jZm9yZXN0MywgY29uZGl0aW9uYWwgPSBUKQ0KIyBjb25kaXRpb25hbCA9IFQgYWRqdXN0cyBmb3IgY29ycmVsYXRpb25zIGJldHdlZW4gcHJlZGljdG9yIHZhcmlhYmxlcw0KIyBUYWtlcyBsb25nIQ0Kc2F2ZSh2YXJpbXBfcmZfY29uZF9jZm9yZXN0MywgZmlsZSA9ICJvYmplY3RzL3ZhcmltcF9yZl9jb25kX2Nmb3Jlc3QzLlJkYXRhIikNCmBgYA0KDQpWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QNCg0KYGBge3J9DQp2YXJpbXBfcmZfY2ZvcmVzdDNfZGYgPC0gZGF0YS5mcmFtZShWYXJpYWJsZSA9IG5hbWVzKHZhcmltcF9yZl9jZm9yZXN0MyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJbXBvcnRhbmNlID0gdmFyaW1wX3JmX2Nmb3Jlc3QzKQ0KZ2dwbG90KHZhcmltcF9yZl9jZm9yZXN0M19kZiwNCiAgICAgICBhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAibGlnaHRibHVlIikgKw0KICBjb29yZF9mbGlwKCkgKyB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlZhcmlhYmxlIEltcG9ydGFuY2UiLCB4ID0gIlZhcmlhYmxlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANCg0KUk9DIGN1cnZlczoNCg0KYGBge3J9DQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgZWFjaCBjbGFzcw0KcHJvYmFiaWxpdGllcyA8LSBwcmVkaWN0KHJmX2Nmb3Jlc3QxLCBuZXdkYXRhID0gdGVzdF9kYXRhMSwgdHlwZSA9ICJwcm9iIikNCg0KIyBTdGVwIDE6IENvbnZlcnQgbGlzdCBvZiBtYXRyaWNlcyB0byBhIHByb3BlciBkYXRhIGZyYW1lDQpwcm9iX21hdHJpeCA8LSB0KHNhcHBseShwcm9iYWJpbGl0aWVzLCBhcy52ZWN0b3IpKQ0KY29sbmFtZXMocHJvYl9tYXRyaXgpIDwtIGMoIlEiLCAiUiIsICJTIiwgIlQiKSAgIyBBZGp1c3QgaWYgbmVlZGVkDQpwcm9iX2RmIDwtIGFzLmRhdGEuZnJhbWUocHJvYl9tYXRyaXgpDQoNCiMgU3RlcCAyOiBQcmVwYXJlIGFjdHVhbCBjbGFzcyBsYWJlbHMNCmFjdHVhbCA8LSBmYWN0b3IodGVzdF9kYXRhMSRFVU5JU2FfMSwgbGV2ZWxzID0gYygiUSIsICJSIiwgIlMiLCAiVCIpKQ0KY2xhc3NlcyA8LSBsZXZlbHMoYWN0dWFsKQ0KDQojIFN0ZXAgMzogQmluYXJpemUgYWN0dWFsIGxhYmVscw0KYWN0dWFsX2JpbiA8LSBtb2RlbC5tYXRyaXgofiBhY3R1YWwgLSAxKQ0KY29sbmFtZXMoYWN0dWFsX2JpbikgPC0gZ3N1YigiYWN0dWFsIiwgIiIsIGNvbG5hbWVzKGFjdHVhbF9iaW4pKQ0KDQojIFN0ZXAgNDogQ29tcHV0ZSBST0MgZGF0YSBmb3IgZWFjaCBjbGFzcyB3aXRoIEFVQyBpbiBsYWJlbA0Kcm9jX2RhdGEgPC0gbGFwcGx5KGNsYXNzZXMsIGZ1bmN0aW9uKGNsYXNzKSB7DQogIHJvY19vYmogPC0gcm9jKGFjdHVhbF9iaW5bLCBjbGFzc10sIHByb2JfZGZbW2NsYXNzXV0pDQogIGF1Y192YWwgPC0gcm91bmQoYXVjKHJvY19vYmopLCAzKQ0KICBkYXRhLmZyYW1lKA0KICAgIEZQUiA9IHJldihyb2Nfb2JqJHNwZWNpZmljaXRpZXMpLA0KICAgIFRQUiA9IHJldihyb2Nfb2JqJHNlbnNpdGl2aXRpZXMpLA0KICAgIENsYXNzID0gcGFzdGUwKGNsYXNzLCAiIChBVUMgPSAiLCBhdWNfdmFsLCAiKSIpDQogICkNCn0pICU+JSBiaW5kX3Jvd3MoKQ0KDQojIFN0ZXAgNTogUGxvdCBST0MgY3VydmVzIHdpdGggZ2dwbG90Mg0Kcm9jMyA8LSBnZ3Bsb3Qocm9jX2RhdGEsIGFlcyh4ID0gRlBSLCB5ID0gVFBSLCBjb2xvciA9IENsYXNzKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJncmF5IikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk11bHRpY2xhc3MgUk9DIEN1cnZlcyB3aXRoIEFVQyIsDQogICAgeCA9ICJGYWxzZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICB5ID0gIlRydWUgUG9zaXRpdmUgUmF0ZSIsDQogICAgY29sb3IgPSAiQ2xhc3MgKEFVQykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCnJvYzMNCmBgYA0KDQojIyBBbGwgR1BTIHBvaW50cyB3aXRoaW4gbWVhbiArLy0gU0QNCg0KRmlsdGVyIHRoZSBkYXRhIHRvIGdldCBvbmx5IEdQUy1wb2ludHMgd2l0aGluIG1lYW4gKy8tIFNEIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQptZWFuX3NkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGdyb3VwX2J5KEVVTklTYV8xKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIG1lYW5fTkRWSV9tYXggPSBtZWFuKGFsbF9HUFNfdmFsaWQkTkRWSV9tYXgsIG5hLnJtID0gVCksDQogICAgbWVhbl9ORE1JX21pbiA9IG1lYW4oYWxsX0dQU192YWxpZCRORE1JX21pbiwgbmEucm0gPSBUKSwNCiAgICBzZF9ORFZJX21heCA9IHNkKGFsbF9HUFNfdmFsaWQkTkRWSV9tYXgsIG5hLnJtID0gVCksDQogICAgc2RfTkRNSV9taW4gPSBzZChhbGxfR1BTX3ZhbGlkJE5ETUlfbWluLCBuYS5ybSA9IFQpDQogICAgKQ0KDQojIEpvaW4gdGhlIElRIHJhbmdlcyBiYWNrIHRvIHRoZSBvcmlnaW5hbCBkYXRhDQphbGxfR1BTX3ZhbGlkIDwtIGFsbF9HUFNfdmFsaWQgJT4lDQogIGxlZnRfam9pbihtZWFuX3NkLCBieSA9ICJFVU5JU2FfMSIpDQoNCiMgRmlsdGVyIHJvd3Mgd2l0aGluIHRoZSBzcGVjaWZpZWQgcmFuZ2UgZm9yIGJvdGggdmFyaWFibGVzDQpmaWx0ZXJlZF9kYXRhNCA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoDQogICAgKE5EVklfbWF4ID49IChtZWFuX05EVklfbWF4IC0gc2RfTkRWSV9tYXgpICYgTkRWSV9tYXggPD0gKG1lYW5fTkRWSV9tYXggKyBzZF9ORFZJX21heCkpICYNCiAgICAgIChORE1JX21pbiA+PSAobWVhbl9ORE1JX21pbiAtIHNkX05ETUlfbWluKSAmIE5ETUlfbWluIDw9IChtZWFuX05ETUlfbWluICsgc2RfTkRNSV9taW4pKQ0KICAgICkgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzNCA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGE0KSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhNCkpDQp0cmFpbl9kYXRhNCA8LSBmaWx0ZXJlZF9kYXRhNFt0cmFpbl9pbmRpY2VzNCwgXQ0KdGVzdF9kYXRhNCA8LSBmaWx0ZXJlZF9kYXRhNFstdHJhaW5faW5kaWNlczQsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhNCAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0NCA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGE0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q0IDwtIHByZWRpY3QocmZfY2ZvcmVzdDQsIG5ld2RhdGEgPSB0ZXN0X2RhdGE0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0NCwgdGVzdF9kYXRhNCRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0NCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3Q0LCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0NCA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3Q0LCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q0LCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDQuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0NF9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3Q0KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDQpDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3Q0X2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGExJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2M0IDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jNA0KYGBgDQoNCiMjIEFsbCBHUFMgcG9pbnRzIGFib3ZlIHAyMCBhbmQgYmVsb3cgcDgwDQoNCkZpbHRlciB0aGUgZGF0YSB0byBnZXQgb25seSBHUFMtcG9pbnRzIGFib3ZlIHAyMCBhbmQgYmVsb3cgcDgwIG9mIE5EVklfbWF4IGFuZCBORE1JX21pbi4NCg0KYGBge3J9DQojIEZpbHRlciByb3dzIGFib3ZlIHRoZSAyMHRoIHBlcmNlbnRpbGUgYW5kIGJlbG93IHRoZSA4MHRoIHBlcmNlbnRpbGUgZm9yIGJvdGggdmFyaWFibGVzDQpmaWx0ZXJlZF9kYXRhNSA8LSBhbGxfR1BTX3ZhbGlkICU+JQ0KICBmaWx0ZXIoDQogICAgKE5EVklfbWF4ID49IHBlcmNlbnRpbGVfMjBfTkRWSV9tYXggJiBORFZJX21heCA8PSBwZXJjZW50aWxlXzgwX05EVklfbWF4KSAmDQogICAgKE5ETUlfbWluID49IHBlcmNlbnRpbGVfMjBfTkRNSV9taW4gJiBORE1JX21pbiA8PSBwZXJjZW50aWxlXzgwX05ETUlfbWluKQ0KICAgICkgJT4lDQogIGZpbHRlcighaXMubmEoTkRWSV9tYXgpICYgIWlzLm5hKE5ETUlfbWF4KSAmICFpcy5uYShORFdJX21heCkgJg0KICAgICAgICAgICAhaXMubmEoU0FWSV9tYXgpICYgIWlzLm5hKEVWSV9tYXgpICYgIWlzLm5hKE5EVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShORE1JX21pbikgJiAhaXMubmEoTkRXSV9taW4pICYgIWlzLm5hKFNBVklfbWluKSAmDQogICAgICAgICAgICFpcy5uYShFVklfbWluKSkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGFzLmZhY3RvcihFVU5JU2FfMSkpICU+JQ0KICBmaWx0ZXIoRVZJX21heCA8PSAxICYgRVZJX21pbiA+PSAtMSkNCmBgYA0KDQpTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgc2V0cy4NCg0KYGBge3J9DQp0cmFpbl9pbmRpY2VzNSA8LSBzYW1wbGUoMTpucm93KGZpbHRlcmVkX2RhdGE1KSwgMC43ICogbnJvdyhmaWx0ZXJlZF9kYXRhNSkpDQp0cmFpbl9kYXRhNSA8LSBmaWx0ZXJlZF9kYXRhNVt0cmFpbl9pbmRpY2VzNSwgXQ0KdGVzdF9kYXRhNSA8LSBmaWx0ZXJlZF9kYXRhNVstdHJhaW5faW5kaWNlczUsIF0NCmBgYA0KDQpOdW1iZXIgb2YgcG9pbnRzIHBlciBjYXRlZ29yeSBmb3IgZmlsdGVyZWQgZGF0YToNCg0KYGBge3J9DQpmaWx0ZXJlZF9kYXRhNSAlPiUgY291bnQoRVVOSVNhXzEpDQpgYGANCg0KYGBge3J9DQpyZl9jZm9yZXN0NSA8LSBwYXJ0eTo6Y2ZvcmVzdChFVU5JU2FfMSB+IE5EVklfbWF4ICsgTkRWSV9taW4gKyBORE1JX21heCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5ETUlfbWluICsgTkRXSV9tYXggKyBORFdJX21pbiArIEVWSV9tYXggKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFVklfbWluICsgU0FWSV9tYXggKyBTQVZJX21pbiArIGNhbm9weV9oZWlnaHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGE1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbHMgPSBjZm9yZXN0X2NvbnRyb2woDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG10cnkgPSBzcXJ0KDExKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIERlZmF1bHQgbXRyeSA9IDUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCYWdnaW5nOiBtdHJ5ID0gTlVMTA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIG9yID0gbnVtYmVyIG9mIGlucHV0IHZhcmlhYmxlcw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBEZWZhdWx0LCB0cnkgaW5jcmVhc2luZw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSANCmBgYA0KDQpgYGB7cn0NCnByZWRpY3Rpb25zX3JmX2Nmb3Jlc3Q1IDwtIHByZWRpY3QocmZfY2ZvcmVzdDUsIG5ld2RhdGEgPSB0ZXN0X2RhdGE1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPT0IgPSBUUlVFLCB0eXBlID0gInJlc3BvbnNlIikNCmBgYA0KDQpDb25mdXNpb24gbWF0cml4Og0KDQpgYGB7cn0NCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZl9jZm9yZXN0NSwgdGVzdF9kYXRhNSRFVU5JU2FfMSkNCmBgYA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0NSA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3Q1LCBjb25kaXRpb25hbCA9IEYpIA0KYGBgDQoNCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9DQp2YXJpbXBfcmZfY29uZF9jZm9yZXN0NSA8LSBwYXJ0eTo6dmFyaW1wKHJmX2Nmb3Jlc3Q1LCBjb25kaXRpb25hbCA9IFQpDQojIGNvbmRpdGlvbmFsID0gVCBhZGp1c3RzIGZvciBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzDQojIFRha2VzIGxvbmchDQpzYXZlKHZhcmltcF9yZl9jb25kX2Nmb3Jlc3Q1LCBmaWxlID0gIm9iamVjdHMvdmFyaW1wX3JmX2NvbmRfY2ZvcmVzdDUuUmRhdGEiKQ0KYGBgDQoNClZhcmlhYmxlIEltcG9ydGFuY2UgUGxvdA0KDQpgYGB7cn0NCnZhcmltcF9yZl9jZm9yZXN0NV9kZiA8LSBkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmFyaW1wX3JmX2Nmb3Jlc3Q1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSB2YXJpbXBfcmZfY2ZvcmVzdDUpDQpnZ3Bsb3QodmFyaW1wX3JmX2Nmb3Jlc3Q1X2RmLA0KICAgICAgIGFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIHkgPSBJbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSIsIHggPSAiVmFyaWFibGVzIiwgeSA9ICJJbXBvcnRhbmNlIikNCmBgYA0KDQpST0MgY3VydmVzOg0KDQpgYGB7cn0NCiMgUHJlZGljdCBwcm9iYWJpbGl0aWVzIGZvciBlYWNoIGNsYXNzDQpwcm9iYWJpbGl0aWVzIDwtIHByZWRpY3QocmZfY2ZvcmVzdDEsIG5ld2RhdGEgPSB0ZXN0X2RhdGExLCB0eXBlID0gInByb2IiKQ0KDQojIFN0ZXAgMTogQ29udmVydCBsaXN0IG9mIG1hdHJpY2VzIHRvIGEgcHJvcGVyIGRhdGEgZnJhbWUNCnByb2JfbWF0cml4IDwtIHQoc2FwcGx5KHByb2JhYmlsaXRpZXMsIGFzLnZlY3RvcikpDQpjb2xuYW1lcyhwcm9iX21hdHJpeCkgPC0gYygiUSIsICJSIiwgIlMiLCAiVCIpICAjIEFkanVzdCBpZiBuZWVkZWQNCnByb2JfZGYgPC0gYXMuZGF0YS5mcmFtZShwcm9iX21hdHJpeCkNCg0KIyBTdGVwIDI6IFByZXBhcmUgYWN0dWFsIGNsYXNzIGxhYmVscw0KYWN0dWFsIDwtIGZhY3Rvcih0ZXN0X2RhdGExJEVVTklTYV8xLCBsZXZlbHMgPSBjKCJRIiwgIlIiLCAiUyIsICJUIikpDQpjbGFzc2VzIDwtIGxldmVscyhhY3R1YWwpDQoNCiMgU3RlcCAzOiBCaW5hcml6ZSBhY3R1YWwgbGFiZWxzDQphY3R1YWxfYmluIDwtIG1vZGVsLm1hdHJpeCh+IGFjdHVhbCAtIDEpDQpjb2xuYW1lcyhhY3R1YWxfYmluKSA8LSBnc3ViKCJhY3R1YWwiLCAiIiwgY29sbmFtZXMoYWN0dWFsX2JpbikpDQoNCiMgU3RlcCA0OiBDb21wdXRlIFJPQyBkYXRhIGZvciBlYWNoIGNsYXNzIHdpdGggQVVDIGluIGxhYmVsDQpyb2NfZGF0YSA8LSBsYXBwbHkoY2xhc3NlcywgZnVuY3Rpb24oY2xhc3MpIHsNCiAgcm9jX29iaiA8LSByb2MoYWN0dWFsX2JpblssIGNsYXNzXSwgcHJvYl9kZltbY2xhc3NdXSkNCiAgYXVjX3ZhbCA8LSByb3VuZChhdWMocm9jX29iaiksIDMpDQogIGRhdGEuZnJhbWUoDQogICAgRlBSID0gcmV2KHJvY19vYmokc3BlY2lmaWNpdGllcyksDQogICAgVFBSID0gcmV2KHJvY19vYmokc2Vuc2l0aXZpdGllcyksDQogICAgQ2xhc3MgPSBwYXN0ZTAoY2xhc3MsICIgKEFVQyA9ICIsIGF1Y192YWwsICIpIikNCiAgKQ0KfSkgJT4lIGJpbmRfcm93cygpDQoNCiMgU3RlcCA1OiBQbG90IFJPQyBjdXJ2ZXMgd2l0aCBnZ3Bsb3QyDQpyb2M1IDwtIGdncGxvdChyb2NfZGF0YSwgYWVzKHggPSBGUFIsIHkgPSBUUFIsIGNvbG9yID0gQ2xhc3MpKSArDQogIGdlb21fbGluZShzaXplID0gMS4yKSArDQogIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gImdyYXkiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTXVsdGljbGFzcyBST0MgQ3VydmVzIHdpdGggQVVDIiwNCiAgICB4ID0gIkZhbHNlIFBvc2l0aXZlIFJhdGUiLA0KICAgIHkgPSAiVHJ1ZSBQb3NpdGl2ZSBSYXRlIiwNCiAgICBjb2xvciA9ICJDbGFzcyAoQVVDKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0Kcm9jNQ0KYGBgDQoNCiMgSEVSRTogQ29tcGFyZSBSRiAxLTUNCg0KIyBDb3JkaWxsZXJhIGRhdGENCg0KYGBge3J9DQpBbHBpbmVHcmFzc2xhbmRzX2luZGljZXMgPC0gcmVhZF9jc3YoDQogICJDOi9EYXRhL01PVElWQVRFL0NvcmRpbGxlcmEvQWxwaW5lR3Jhc3NsYW5kcy9BbHBpbmVHcmFzc2xhbmRfU2VudGluZWxfUGxvdF9BbGx5ZWFyX0FsbG1ldHJpY3MuY3N2IikNCkFscGluZUdyYXNzbGFuZHNfcGhlbiA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9BbHBpbmVHcmFzc2xhbmRzL0FscGluZUdyYXNzbGFuZHNfUGhlbm9sb2d5X1NPU19FT1NfUGVha19ORFZJX0FtcGxpdHVkZS5jc3YiKQ0KQWxwaW5lR3Jhc3NsYW5kc19DSCA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9BbHBpbmVHcmFzc2xhbmRzL0FscGluZUdyYXNzbGFuZHNfQ2Fub3B5SGVpZ2h0XzFtLmNzdiIpDQpWZWdldGF0aW9uVHlwZXNfaW5kaWNlcyA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9WZWdldGF0aW9uVHlwZXMvVmVnZXRhdGlvblR5cGVzX1NlbnRpbmVsX1Bsb3RfQWxsWWVhcl9BbGxtZXRyaWNzLmNzdiIpDQpWZWdldGF0aW9uVHlwZXNfcGhlbiA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9WZWdldGF0aW9uVHlwZXMvVmVnZXRhdGlvblR5cGVzX1BoZW5vbG9neV9TT1NfRU9TX1BlYWtfTkRWSV9BbXBsaXR1ZGUuY3N2IikNClZlZ2V0YXRpb25UeXBlc19DSCA8LSByZWFkX2NzdigNCiAgIkM6L0RhdGEvTU9USVZBVEUvQ29yZGlsbGVyYS9WZWdldGF0aW9uVHlwZXMvVmVnZXRhdGlvblR5cGVzX0Nhbm9weUhlaWdodF8xbS5jc3YiKQ0KYGBgDQoNCmBgYHtyfQ0KQWxwaW5lR3Jhc3NsYW5kcyA8LSBBbHBpbmVHcmFzc2xhbmRzX2luZGljZXMgJT4lDQogIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvLCAtTG9jYWxpZGFkKSAlPiUNCiAgcmVuYW1lKEjDoWJpdGF0ID0gIkjvv71iaXRhdCIpICU+JSANCiAgZnVsbF9qb2luKEFscGluZUdyYXNzbGFuZHNfcGhlbiAgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtYHN5c3RlbTppbmRleGAsIC0uZ2VvLCAtTG9jYWxpZGFkKSAlPiUNCiAgICAgICAgICAgICAgcmVuYW1lKEjDoWJpdGF0ID0gIkjvv71iaXRhdCIpKSAlPiUNCiAgZnVsbF9qb2luKEFscGluZUdyYXNzbGFuZHNfQ0ggICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbywgLUxvY2FsaWRhZCkpICU+JQ0KICBzZWxlY3QoLURhdGVfX3llYXIsIC0gYFByZWNpc2nvv71uYCkgJT4lDQogIG11dGF0ZShEQVRFID0geW1kKERBVEUpKSAlPiUNCiAgcmVuYW1lKElEID0gIlJlbGV2ZV9udW0iKSAlPiUNCiAgbXV0YXRlKElEID0gYXMuY2hhcmFjdGVyKElEKSkgJT4lDQogIG11dGF0ZShsYXllciA9ICJBbHBpbmVHcmFzc2xhbmRzIikNCmBgYA0KDQpgYGB7cn0NClZlZ2V0YXRpb25UeXBlcyA8LSBWZWdldGF0aW9uVHlwZXNfaW5kaWNlcyAlPiUNCiAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8pICU+JQ0KICBmdWxsX2pvaW4oVmVnZXRhdGlvblR5cGVzX3BoZW4gICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWBzeXN0ZW06aW5kZXhgLCAtLmdlbykpICU+JQ0KICBmdWxsX2pvaW4oVmVnZXRhdGlvblR5cGVzX0NIICAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KC1gc3lzdGVtOmluZGV4YCwgLS5nZW8pKSAlPiUNCiAgcmVuYW1lKEjDoWJpdGF0ID0gIlRZUEUiKSAlPiUNCiAgbXV0YXRlKGxheWVyID0gIlZlZ2V0YXRpb25UeXBlcyIpDQpgYGANCg0KTWVyZ2UgYm90aCBkYXRhc2V0czoNCg0KYGBge3J9DQpjb3JkaWxsZXJhIDwtIGJpbmRfcm93cygNCiAgQWxwaW5lR3Jhc3NsYW5kcyAlPiUgc2VsZWN0KERBVEUsIElELCBzdGFydHNfd2l0aCgiTkRNSSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRzX3dpdGgoIk5EVkkiKSwgSMOhYml0YXQsICJFT1NfRE9ZIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQZWFrX0RPWSIsICJTT1NfRE9ZIiwgIlNlYXNvbl9MZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNhbm9weV9oZWlnaHQiLCAibGF5ZXIiKSwNCiAgVmVnZXRhdGlvblR5cGVzICU+JSBzZWxlY3QoREFURSwgSUQsIHN0YXJ0c193aXRoKCJORE1JIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydHNfd2l0aCgiTkRWSSIpLCBIw6FiaXRhdCwgIkVPU19ET1kiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBlYWtfRE9ZIiwgIlNPU19ET1kiLCAiU2Vhc29uX0xlbmd0aCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY2Fub3B5X2hlaWdodCIsICJsYXllciIpDQogICkgJT4lDQogIG11dGF0ZShFVU5JU2FfMSA9IGNhc2Vfd2hlbigNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJQYXN0aXphbHxDZXJ2dW5hbHxncmFzc2xhbmR8bWVhZG93IikgfiAiUiIsDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiZm9yZXN0IikgfiAiVCIsDQogICAgSMOhYml0YXQgPSBzdHJfZGV0ZWN0KEjDoWJpdGF0LCAiU2NydWJ8c2NydWJ8U2hydWJsYW5kfHNocnVibGFuZHxzaHJ1YnxIZWF0aGxhbmQiKSB+ICJTIiwNCiAgICBIw6FiaXRhdCA9IHN0cl9kZXRlY3QoSMOhYml0YXQsICJTdWVsb3xTY3JlZXxzY3JlZXxjbGlmZiIpIH4gIlUiLA0KICAgIEjDoWJpdGF0ID0gaXMubmEoSMOhYml0YXQpIH4gIlIiLA0KICAgIFRSVUUgfiBOQV9jaGFyYWN0ZXJfKSwNCiAgICBFVU5JU2FfMV9kZXNjciA9IGNhc2Vfd2hlbigNCiAgICAgIEVVTklTYV8xID09ICJSIiB+ICJHcmFzc2xhbmRzIiwNCiAgICAgIEVVTklTYV8xID09ICJUIiB+ICJGb3Jlc3RzIGFuZCBvdGhlciB3b29kZWQgbGFuZCIsDQogICAgICBFVU5JU2FfMSA9PSAiUyIgfiAiSGVhdGhsYW5kcywgc2NydWIgYW5kIHR1bmRyYSIsDQogICAgICBFVU5JU2FfMSA9PSAiVSIgfiAiSW5sYW5kIGhhYml0YXRzIHdpdGggbm8gb3IgbGl0dGxlIHNvaWwiKQ0KICAgICkNCmBgYA0KDQojIyBORFZJLCBORE1JDQoNCmBgYHtyfQ0KZGlzdHJfcGxvdChjb3JkaWxsZXJhLA0KICAgICAgICAgICBjKCJORFZJX21heCIsICJORFZJX3A5MCIsICJORFZJX21pbiIsICJORFZJX3AxMCIpLCANCiAgICAgICAgICAgYygiTkRWSSBtYXgiLCAiTkRWSSBwOTAiLCAiTkRWSSBtaW4iLCAiTkRWSSBwMTAiKSkNCmRpc3RyX3Bsb3QoY29yZGlsbGVyYSwNCiAgICAgICAgICAgYygiTkRNSV9tYXgiLCAiTkRNSV9wOTAiLCAiTkRNSV9taW4iLCAiTkRNSV9wMTAiKSwgDQogICAgICAgICAgIGMoIk5ETUkgbWF4IiwgIk5ETUkgcDkwIiwgIk5ETUkgbWluIiwgIk5ETUkgcDEwIikpDQpgYGANCg0KIyBTZXNzaW9uIGluZm8NCg0KYGBge3J9DQpzZXNzaW9uSW5mbygpDQpgYGANCg0K